mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-06-22 12:23:04 -07:00
Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2481878892 | |||
| 04359fbf31 | |||
| 80ea12ed48 | |||
| 9d2d4b7d5f | |||
| 3de2862655 | |||
| abb4def848 | |||
| 04decabc46 | |||
| 334ff52084 | |||
| a931ad7a1e | |||
| 069311dcf3 | |||
| b7e8cbea20 | |||
| 9905eff383 | |||
| 48320197f9 | |||
| 9c9a835f33 | |||
| ec477b916b | |||
| dc5a1fcb9a | |||
| 20a51f980b | |||
| 7604787fbb |
+44
-2
@@ -1,7 +1,45 @@
|
||||
# Changelog
|
||||
|
||||
## v. 0.2.0 | WIP
|
||||
|
||||

|
||||
|
||||
### App
|
||||
|
||||
- General
|
||||
- Added height datasets and many optimizations to make them usable but only available on desktop and tablets for now
|
||||
- Added a light theme
|
||||
- Charts
|
||||
- Added split panes in order to have the vertical axis visible for all datasets
|
||||
- Added min and max values on the charts
|
||||
- Fixed legend hovering on mobile not resetting on touch end
|
||||
- Added "3 months" and yearly time scale setters (from year 2009 to today)
|
||||
- Hide scrollbar of timescale setters and instead added scroll buttons to the legend only visible on desktop
|
||||
- Improved Share/QR Code screen
|
||||
- Changed all Area series to Line series
|
||||
- Fixed horizontal scrollable legend not updating on preset change
|
||||
- Performance
|
||||
- Improved app's reactivity
|
||||
- Added some chunk splitting for a faster initial load
|
||||
- Global improvements that increased the Lighthouse's performance score
|
||||
- Settings
|
||||
- Finally made a proper component where you can chose the app's theme, between a moving or static background and its text opacity
|
||||
- Added donations section with a leaderboard
|
||||
- Added various links that are visible on the bottom side of the strip on desktop to mobile users
|
||||
- Added install instructions when not installed for Apple users
|
||||
- Misc
|
||||
- Support mini window size, could be useful for embedded views
|
||||
- Hopefully made scrollbars a little more subtle on WIndows and Linux, can't test
|
||||
- Generale style updates
|
||||
|
||||
### Parser
|
||||
|
||||
- Fixed ulimit only being run in Mac OS instead of whenever the program is detected
|
||||
|
||||
## v. 0.1.1 | 849240 - 2024/06/24
|
||||
|
||||

|
||||
|
||||
### Parser
|
||||
|
||||
- Fixed overflow in `Price` struct which caused many Realized Caps and Realized Prices to have completely bogus data
|
||||
@@ -9,7 +47,7 @@
|
||||
|
||||
### Server
|
||||
|
||||
- Added the chunk, date and time in the terminal logs
|
||||
- Added the chunk, date and time of the request to the terminal logs
|
||||
|
||||
### App
|
||||
|
||||
@@ -36,7 +74,7 @@
|
||||
- Strip
|
||||
- Temporarily removed the Home button on the strip bar on desktop as there is no landing page yet
|
||||
- Settings
|
||||
- Add version
|
||||
- Added version
|
||||
- PWA
|
||||
- Fixed background update
|
||||
- Changed update check frequency to 1 minute (~1kb to fetch every minute which is very reasonable)
|
||||
@@ -47,3 +85,7 @@
|
||||
### Price
|
||||
|
||||
- Deleted old price datasets and their backups
|
||||
|
||||
## v. 0.1.0 | 848642 - 2024/06/19
|
||||
|
||||

|
||||
|
||||
@@ -1,44 +1,86 @@
|
||||
# SATONOMICS
|
||||
|
||||

|
||||
|
||||
## Description
|
||||
|
||||
TLDR: Free, open source, verifiable and self-hostable Bitcoin on-chain data generator and visualizer
|
||||
Satonomics is a better, FOSS, Bitcoin-only, self-hostable Glassnode.
|
||||
|
||||
Satonomics is an open-source suite of tools that computes, distributes, and displays on-chain data, making it freely available for anyone to use.
|
||||
While [mempool.space](https://mempool.space) gives a very micro view of the network where you can follow the journey of any address, this tool is the exact opposite and very complimentary by giving you a much more global/macro view of the flow and various dynamics of the network via thousands of charts.
|
||||
|
||||
The generated datasets are incredibly diverse and can be used for a wide range of purposes. Whether you're looking to conduct a health check on the network, gain insights into its current or past state, or leverage the data for trading purposes, these tools offer various charts, dashboards (Soon TM), and an extensive API to help you achieve your goals.
|
||||
To promote even more transparency and trust in the network, this project is committed to making on-chain data accessible and verifiable by all, no matter your intentions or financial situation. That is why, the whole project is completely free, from code to services, including a real-time API with thousands and thousands of routes which can be used at will.
|
||||
|
||||
To promote transparency and trust in the network, this project is committed to making on-chain data accessible and verifiable to all, without discrimination and is a great complimentary tool to [mempool.space](https://mempool.space).
|
||||
**Having anyone be able to easily do a health-check of the network is incredibly important and should be wanted by every single bitcoiner.**
|
||||
|
||||
## Warning
|
||||
|
||||
This project is in a very early stage. The web app will have bugs, the API might break and the data can definitely to be false or slightly false.
|
||||
|
||||
## Instances
|
||||
|
||||
Web App:
|
||||
|
||||
- [app.satonomics.xyz](https://app.satonomics.xyz)
|
||||
|
||||
API:
|
||||
|
||||
- [api.satonomics.xyz](https://api.satonomics.xyz)
|
||||
|
||||
## Structure
|
||||
|
||||
- `parser`: The backbone of the project, it does most of the work by parsing and then computing datasets from the timechain.
|
||||
- `server`: A small server which automatically creates routes to access through an API all created datasets.
|
||||
- `app`: A web app which displays the generated datasets in various charts.
|
||||
- `app`: A web app which displays the generated datasets in various charts and dashboards.
|
||||
|
||||
## Git
|
||||
## Goals / Philosophy
|
||||
|
||||
- [Repository](https://codeberg.org/satonomics/satonomics)
|
||||
- [Issues](https://gitworkshop.dev/r/naddr1qq99xct5dahx7mtfvdesz9thwden5te0wp6hyurvv4ex2mrp0yhxxmmdqgsfw5dacngjlahye34krvgz7u0yghhjgk7gxzl5ptm9v6n2y3sn03srqsqqqaueek2h03/issues)
|
||||
- [Proposals](https://gitworkshop.dev/r/naddr1qq99xct5dahx7mtfvdesz9thwden5te0wp6hyurvv4ex2mrp0yhxxmmdqgsfw5dacngjlahye34krvgz7u0yghhjgk7gxzl5ptm9v6n2y3sn03srqsqqqaueek2h03/proposals)
|
||||
Adjectives that describe what this project is or strives to be, in no particular order:
|
||||
|
||||
## Goals
|
||||
- **Best**: Replace Glassnode as the go to
|
||||
- **Diverse**: Have as many charts/datasets as possible and something for everyone
|
||||
- **Free**: Is and always will be completely free
|
||||
- **Open**: With a very permissive license
|
||||
- **Trustless**: You can verify and see exactly how each dataset is computed
|
||||
- **Independent**: Only one, easily swappable, dependency (Price API)
|
||||
- **Educational**: By providing many datasets that can be used to describe how Bitcoin works and why
|
||||
- **Timeless**: Be relevant and usable 10 years from now by being independent and not do address tagging
|
||||
- **Sovereign**: Be self-hostable on accessible hardware
|
||||
- **Versatile**: You can view the data in charts, you can download the data, you can fetch the data via an API
|
||||
- **Accessible**: Free Website and API with all the datasets for everyone
|
||||
|
||||
- Be the absolute best on-chain data source and app
|
||||
- Have as many datasets and charts as possible
|
||||
- Be self-hostable on cheap computers
|
||||
- Be runnable on a machine with 8 GB RAM (16 GB RAM is already possible right now)
|
||||
- Still being runnable 10 years from now
|
||||
- By not relying on any third-party dependencies besides price APIs (which are and should be very common and easy to update)
|
||||
- By **NOT** doing address labelling/tagging (which means **NO** exchange or any other individual address tracking), for that please use [mempool.space](https://mempool.space) or any other tool
|
||||
## Milestones
|
||||
|
||||
Big features that are planned, in no particular order:
|
||||
|
||||
- **Homepage**: A landing page to explains the project and what it does
|
||||
- **More Datasets/Charts**: If a dataset can be computed, it should exist and have its related charts
|
||||
- **Dashboards**: For a quick and real-time view of the latest data of all the datasets
|
||||
- **NOSTR integration**: First to save preferences, later to add some social functionnality
|
||||
- **Datasets by block timestamp**: In addition to having datasets by block date and block height
|
||||
- **Descriptions**: Add text to describe all charts and what they mean
|
||||
- **Start9 Add-on**: By making the whole suite much easier to self-host, it's quite rough right now
|
||||
|
||||
_Maybe_:
|
||||
|
||||
- A Desktop app
|
||||
- A mobile app
|
||||
|
||||
## Brand
|
||||
|
||||
- **Name**: Willing to change if someone thinks of something better !
|
||||
- **Logo**: Most likely a placeholder
|
||||
|
||||
## Collaboration
|
||||
|
||||
- Repositories:
|
||||
- [Github](https://github.com/satonomics-org/satonomics)
|
||||
- [Codeberg](https://codeberg.org/satonomics/satonomics)
|
||||
- Issues:
|
||||
- [Github](https://github.com/satonomics-org/satonomics/issues)
|
||||
- [NOSTR](https://gitworkshop.dev/r/naddr1qq99xct5dahx7mtfvdesz9thwden5te0wp6hyurvv4ex2mrp0yhxxmmdqgsfw5dacngjlahye34krvgz7u0yghhjgk7gxzl5ptm9v6n2y3sn03srqsqqqaueek2h03/issues)
|
||||
- Proposals:
|
||||
- [Github](https://github.com/satonomics-org/satonomics/pulls)
|
||||
- [NOSTR](https://gitworkshop.dev/r/naddr1qq99xct5dahx7mtfvdesz9thwden5te0wp6hyurvv4ex2mrp0yhxxmmdqgsfw5dacngjlahye34krvgz7u0yghhjgk7gxzl5ptm9v6n2y3sn03srqsqqqaueek2h03/proposals)
|
||||
|
||||
## Proof of Work
|
||||
|
||||
|
||||
+6
-3
@@ -1,11 +1,11 @@
|
||||
<!doctype html>
|
||||
<html lang="en" class="overflow-hidden bg-black text-white">
|
||||
<html lang="en" class="overflow-hidden">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Satonomics</title>
|
||||
<meta
|
||||
name="description"
|
||||
content="An app to visualize Bitcoin on-chain data"
|
||||
content="A better, FOSS, Bitcoin-only, self-hostable Glassnode"
|
||||
/>
|
||||
<meta
|
||||
name="viewport"
|
||||
@@ -362,7 +362,10 @@
|
||||
media="(prefers-color-scheme: dark) and (device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
|
||||
/>
|
||||
</head>
|
||||
<body style="font-size: 15px; line-height: 22px">
|
||||
<body
|
||||
class="text-high-contrast bg-white dark:bg-black"
|
||||
style="font-size: 15px; line-height: 22px"
|
||||
>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
|
||||
<div id="root"></div>
|
||||
|
||||
+9
-10
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "satonomics",
|
||||
"description": "Satoshi Economics",
|
||||
"version": "0.1.1",
|
||||
"description": "A better, FOSS, Bitcoin-only, self-hostable Glassnode",
|
||||
"version": "0.2.0",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
@@ -19,30 +19,29 @@
|
||||
"@leeoniya/ufuzzy": "^1.0.14",
|
||||
"@solid-primitives/event-listener": "^2.3.3",
|
||||
"@solid-primitives/intersection-observer": "^2.1.6",
|
||||
"@solid-primitives/memo": "^1.3.8",
|
||||
"@solid-primitives/resize-observer": "^2.0.25",
|
||||
"lean-qr": "^2.3.4",
|
||||
"lightweight-charts": "^4.1.6",
|
||||
"solid-js": "^1.8.17"
|
||||
"solid-js": "^1.8.18"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ianvs/prettier-plugin-sort-imports": "^4.2.1",
|
||||
"@iconify-json/tabler": "^1.1.114",
|
||||
"@ianvs/prettier-plugin-sort-imports": "^4.3.0",
|
||||
"@iconify-json/tabler": "^1.1.116",
|
||||
"@tailwindcss/container-queries": "^0.1.1",
|
||||
"autoprefixer": "^10.4.19",
|
||||
"postcss": "^8.4.38",
|
||||
"postcss": "^8.4.39",
|
||||
"prettier": "^3.3.2",
|
||||
"prettier-plugin-tailwindcss": "^0.6.5",
|
||||
"pwa-asset-generator": "^6.3.1",
|
||||
"rollup-plugin-visualizer": "^5.12.0",
|
||||
"tailwindcss": "^3.4.4",
|
||||
"typescript": "^5.5.2",
|
||||
"typescript": "^5.5.3",
|
||||
"unplugin-auto-import": "^0.17.6",
|
||||
"unplugin-icons": "^0.19.0",
|
||||
"vite": "^5.3.1",
|
||||
"vite": "^5.3.3",
|
||||
"vite-plugin-pwa": "^0.20.0",
|
||||
"vite-plugin-solid": "^2.10.2",
|
||||
"workbox-window": "^7.1.0",
|
||||
"wrangler": "^3.61.0"
|
||||
"wrangler": "^3.63.1"
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+168
-184
@@ -10,16 +10,13 @@ dependencies:
|
||||
version: 1.0.14
|
||||
'@solid-primitives/event-listener':
|
||||
specifier: ^2.3.3
|
||||
version: 2.3.3(solid-js@1.8.17)
|
||||
version: 2.3.3(solid-js@1.8.18)
|
||||
'@solid-primitives/intersection-observer':
|
||||
specifier: ^2.1.6
|
||||
version: 2.1.6(solid-js@1.8.17)
|
||||
'@solid-primitives/memo':
|
||||
specifier: ^1.3.8
|
||||
version: 1.3.8(solid-js@1.8.17)
|
||||
version: 2.1.6(solid-js@1.8.18)
|
||||
'@solid-primitives/resize-observer':
|
||||
specifier: ^2.0.25
|
||||
version: 2.0.25(solid-js@1.8.17)
|
||||
version: 2.0.25(solid-js@1.8.18)
|
||||
lean-qr:
|
||||
specifier: ^2.3.4
|
||||
version: 2.3.4
|
||||
@@ -27,31 +24,31 @@ dependencies:
|
||||
specifier: ^4.1.6
|
||||
version: 4.1.6
|
||||
solid-js:
|
||||
specifier: ^1.8.17
|
||||
version: 1.8.17
|
||||
specifier: ^1.8.18
|
||||
version: 1.8.18
|
||||
|
||||
devDependencies:
|
||||
'@ianvs/prettier-plugin-sort-imports':
|
||||
specifier: ^4.2.1
|
||||
version: 4.2.1(prettier@3.3.2)
|
||||
specifier: ^4.3.0
|
||||
version: 4.3.0(prettier@3.3.2)
|
||||
'@iconify-json/tabler':
|
||||
specifier: ^1.1.114
|
||||
version: 1.1.114
|
||||
specifier: ^1.1.116
|
||||
version: 1.1.116
|
||||
'@tailwindcss/container-queries':
|
||||
specifier: ^0.1.1
|
||||
version: 0.1.1(tailwindcss@3.4.4)
|
||||
autoprefixer:
|
||||
specifier: ^10.4.19
|
||||
version: 10.4.19(postcss@8.4.38)
|
||||
version: 10.4.19(postcss@8.4.39)
|
||||
postcss:
|
||||
specifier: ^8.4.38
|
||||
version: 8.4.38
|
||||
specifier: ^8.4.39
|
||||
version: 8.4.39
|
||||
prettier:
|
||||
specifier: ^3.3.2
|
||||
version: 3.3.2
|
||||
prettier-plugin-tailwindcss:
|
||||
specifier: ^0.6.5
|
||||
version: 0.6.5(@ianvs/prettier-plugin-sort-imports@4.2.1)(prettier@3.3.2)
|
||||
version: 0.6.5(@ianvs/prettier-plugin-sort-imports@4.3.0)(prettier@3.3.2)
|
||||
pwa-asset-generator:
|
||||
specifier: ^6.3.1
|
||||
version: 6.3.1
|
||||
@@ -62,8 +59,8 @@ devDependencies:
|
||||
specifier: ^3.4.4
|
||||
version: 3.4.4
|
||||
typescript:
|
||||
specifier: ^5.5.2
|
||||
version: 5.5.2
|
||||
specifier: ^5.5.3
|
||||
version: 5.5.3
|
||||
unplugin-auto-import:
|
||||
specifier: ^0.17.6
|
||||
version: 0.17.6(rollup@2.79.1)
|
||||
@@ -71,20 +68,20 @@ devDependencies:
|
||||
specifier: ^0.19.0
|
||||
version: 0.19.0
|
||||
vite:
|
||||
specifier: ^5.3.1
|
||||
version: 5.3.1
|
||||
specifier: ^5.3.3
|
||||
version: 5.3.3
|
||||
vite-plugin-pwa:
|
||||
specifier: ^0.20.0
|
||||
version: 0.20.0(vite@5.3.1)(workbox-build@7.1.1)(workbox-window@7.1.0)
|
||||
version: 0.20.0(vite@5.3.3)(workbox-build@7.1.1)(workbox-window@7.1.0)
|
||||
vite-plugin-solid:
|
||||
specifier: ^2.10.2
|
||||
version: 2.10.2(solid-js@1.8.17)(vite@5.3.1)
|
||||
version: 2.10.2(solid-js@1.8.18)(vite@5.3.3)
|
||||
workbox-window:
|
||||
specifier: ^7.1.0
|
||||
version: 7.1.0
|
||||
wrangler:
|
||||
specifier: ^3.61.0
|
||||
version: 3.61.0
|
||||
specifier: ^3.63.1
|
||||
version: 3.63.1
|
||||
|
||||
packages:
|
||||
|
||||
@@ -114,8 +111,8 @@ packages:
|
||||
'@jsdevtools/ez-spawn': 3.0.4
|
||||
dev: true
|
||||
|
||||
/@antfu/utils@0.7.8:
|
||||
resolution: {integrity: sha512-rWQkqXRESdjXtc+7NRfK9lASQjpXJu1ayp7qi1d23zZorY+wBHVLHHoVcMsEnkqEBWTFqbztO7/QdJFzyEcLTg==}
|
||||
/@antfu/utils@0.7.10:
|
||||
resolution: {integrity: sha512-+562v9k4aI80m1+VuMHehNJWLOFjBnXn3tdOitzD0il5b7smkSBal4+a3oKiQTbrwMmN/TBUMDvbdoWDehgOww==}
|
||||
dev: true
|
||||
|
||||
/@apideck/better-ajv-errors@0.3.6(ajv@8.16.0):
|
||||
@@ -1383,15 +1380,15 @@ packages:
|
||||
to-fast-properties: 2.0.0
|
||||
dev: true
|
||||
|
||||
/@cloudflare/kv-asset-handler@0.3.3:
|
||||
resolution: {integrity: sha512-wpE+WiWW2kUNwNE0xyl4CtTAs+STjGtouHGiZPGRaisGB7eXXdbvfZdOrQJQVKgTxZiNAgVgmc7fj0sUmd8zyA==}
|
||||
/@cloudflare/kv-asset-handler@0.3.4:
|
||||
resolution: {integrity: sha512-YLPHc8yASwjNkmcDMQMY35yiWjoKAKnhUbPRszBRS0YgH+IXtsMp61j+yTcnCE3oO2DgP0U3iejLC8FTtKDC8Q==}
|
||||
engines: {node: '>=16.13'}
|
||||
dependencies:
|
||||
mime: 3.0.0
|
||||
dev: true
|
||||
|
||||
/@cloudflare/workerd-darwin-64@1.20240610.1:
|
||||
resolution: {integrity: sha512-YanZ1iXgMGaUWlleB5cswSE6qbzyjQ8O7ENWZcPAcZZ6BfuL7q3CWi0t9iM1cv2qx92rRztsRTyjcfq099++XQ==}
|
||||
/@cloudflare/workerd-darwin-64@1.20240701.0:
|
||||
resolution: {integrity: sha512-XAZa4ZP+qyTn6JQQACCPH09hGZXP2lTnWKkmg5mPwT8EyRzCKLkczAf98vPP5bq7JZD/zORdFWRY0dOTap8zTQ==}
|
||||
engines: {node: '>=16'}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
@@ -1399,8 +1396,8 @@ packages:
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@cloudflare/workerd-darwin-arm64@1.20240610.1:
|
||||
resolution: {integrity: sha512-bRe/y/LKjIgp3L2EHjc+CvoCzfHhf4aFTtOBkv2zW+VToNJ4KlXridndf7LvR9urfsFRRo9r4TXCssuKaU+ypQ==}
|
||||
/@cloudflare/workerd-darwin-arm64@1.20240701.0:
|
||||
resolution: {integrity: sha512-w80ZVAgfH4UwTz7fXZtk7KmS2FzlXniuQm4ku4+cIgRTilBAuKqjpOjwUCbx5g13Gqcm9NuiHce+IDGtobRTIQ==}
|
||||
engines: {node: '>=16'}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
@@ -1408,8 +1405,8 @@ packages:
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@cloudflare/workerd-linux-64@1.20240610.1:
|
||||
resolution: {integrity: sha512-2zDcadR7+Gs9SjcMXmwsMji2Xs+yASGNA2cEHDuFc4NMUup+eL1mkzxc/QzvFjyBck98e92rBjMZt2dVscpGKg==}
|
||||
/@cloudflare/workerd-linux-64@1.20240701.0:
|
||||
resolution: {integrity: sha512-UWLr/Anxwwe/25nGv451MNd2jhREmPt/ws17DJJqTLAx6JxwGWA15MeitAIzl0dbxRFAJa+0+R8ag2WR3F/D6g==}
|
||||
engines: {node: '>=16'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
@@ -1417,8 +1414,8 @@ packages:
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@cloudflare/workerd-linux-arm64@1.20240610.1:
|
||||
resolution: {integrity: sha512-7y41rPi5xmIYJN8CY+t3RHnjLL0xx/WYmaTd/j552k1qSr02eTE2o/TGyWZmGUC+lWnwdPQJla0mXbvdqgRdQg==}
|
||||
/@cloudflare/workerd-linux-arm64@1.20240701.0:
|
||||
resolution: {integrity: sha512-3kCnF9kYgov1ggpuWbgpXt4stPOIYtVmPCa7MO2xhhA0TWP6JDUHRUOsnmIgKrvDjXuXqlK16cdg3v+EWsaPJg==}
|
||||
engines: {node: '>=16'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
@@ -1426,8 +1423,8 @@ packages:
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@cloudflare/workerd-windows-64@1.20240610.1:
|
||||
resolution: {integrity: sha512-B0LyT3DB6rXHWNptnntYHPaoJIy0rXnGfeDBM3nEVV8JIsQrx8MEFn2F2jYioH1FkUVavsaqKO/zUosY3tZXVA==}
|
||||
/@cloudflare/workerd-windows-64@1.20240701.0:
|
||||
resolution: {integrity: sha512-6IPGITRAeS67j3BH1rN4iwYWDt47SqJG7KlZJ5bB4UaNAia4mvMBSy/p2p4vA89bbXoDRjMtEvRu7Robu6O7hQ==}
|
||||
engines: {node: '>=16'}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
@@ -1870,8 +1867,8 @@ packages:
|
||||
engines: {node: '>=14'}
|
||||
dev: true
|
||||
|
||||
/@ianvs/prettier-plugin-sort-imports@4.2.1(prettier@3.3.2):
|
||||
resolution: {integrity: sha512-NKN1LVFWUDGDGr3vt+6Ey3qPeN/163uR1pOPAlkWpgvAqgxQ6kSdUf1F0it8aHUtKRUzEGcK38Wxd07O61d7+Q==}
|
||||
/@ianvs/prettier-plugin-sort-imports@4.3.0(prettier@3.3.2):
|
||||
resolution: {integrity: sha512-OOMtUcO4J3LoL63dOKAe7bn+lSRRPeit2DqNHpx+wvBp3Grejo2PMaK4Mp1mwy8pnat64ccSgk/lBZbsAdLErw==}
|
||||
peerDependencies:
|
||||
'@vue/compiler-sfc': 2.7.x || 3.x
|
||||
prettier: 2 || 3
|
||||
@@ -1890,8 +1887,8 @@ packages:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
/@iconify-json/tabler@1.1.114:
|
||||
resolution: {integrity: sha512-AaTTGEyiPQ7VAYyXGQ9jUI8+8iL6xanucYsACz6f3U6JLph6jDyicXXUh+dYM6HxW6TGehwVqRO2NSIQpACszw==}
|
||||
/@iconify-json/tabler@1.1.116:
|
||||
resolution: {integrity: sha512-p+dJ+3L/M2o10REG2lh179Blu5+AA51TFkwuUwY7F+vQsF5Z8DIjyNck3yoBBiCxWqhDhsLzC+p9YO7dWqISmw==}
|
||||
dependencies:
|
||||
'@iconify/types': 2.0.0
|
||||
dev: true
|
||||
@@ -1904,7 +1901,7 @@ packages:
|
||||
resolution: {integrity: sha512-Y+iGko8uv/Fz5bQLLJyNSZGOdMW0G7cnlEX1CiNcKsRXX9cq/y/vwxrIAtLCZhKHr3m0VJmsjVPsvnM4uX8YLg==}
|
||||
dependencies:
|
||||
'@antfu/install-pkg': 0.1.1
|
||||
'@antfu/utils': 0.7.8
|
||||
'@antfu/utils': 0.7.10
|
||||
'@iconify/types': 2.0.0
|
||||
debug: 4.3.5
|
||||
kolorist: 1.8.0
|
||||
@@ -2233,78 +2230,60 @@ packages:
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@solid-primitives/event-listener@2.3.3(solid-js@1.8.17):
|
||||
/@solid-primitives/event-listener@2.3.3(solid-js@1.8.18):
|
||||
resolution: {integrity: sha512-DAJbl+F0wrFW2xmcV8dKMBhk9QLVLuBSW+TR4JmIfTaObxd13PuL7nqaXnaYKDWOYa6otB00qcCUIGbuIhSUgQ==}
|
||||
peerDependencies:
|
||||
solid-js: ^1.6.12
|
||||
dependencies:
|
||||
'@solid-primitives/utils': 6.2.3(solid-js@1.8.17)
|
||||
solid-js: 1.8.17
|
||||
'@solid-primitives/utils': 6.2.3(solid-js@1.8.18)
|
||||
solid-js: 1.8.18
|
||||
dev: false
|
||||
|
||||
/@solid-primitives/intersection-observer@2.1.6(solid-js@1.8.17):
|
||||
/@solid-primitives/intersection-observer@2.1.6(solid-js@1.8.18):
|
||||
resolution: {integrity: sha512-SeiCmN/R46Z+o9+5HhIQzSor0DqVPyo4ROLQMvCI8AsGZl/5nHlWzHTTbWPeukVUXTgb04wfC3DUo9IzF/XloA==}
|
||||
peerDependencies:
|
||||
solid-js: ^1.6.12
|
||||
dependencies:
|
||||
'@solid-primitives/utils': 6.2.3(solid-js@1.8.17)
|
||||
solid-js: 1.8.17
|
||||
'@solid-primitives/utils': 6.2.3(solid-js@1.8.18)
|
||||
solid-js: 1.8.18
|
||||
dev: false
|
||||
|
||||
/@solid-primitives/memo@1.3.8(solid-js@1.8.17):
|
||||
resolution: {integrity: sha512-U75pfLFSxFmM2xbx1+2XPPyWbaXrnUFF10spbFuOUgJ7azrC+4y+FnrVi4RKqHw9gftd8aKQuTiyMQq468YLQw==}
|
||||
peerDependencies:
|
||||
solid-js: ^1.6.12
|
||||
dependencies:
|
||||
'@solid-primitives/scheduled': 1.4.3(solid-js@1.8.17)
|
||||
'@solid-primitives/utils': 6.2.3(solid-js@1.8.17)
|
||||
solid-js: 1.8.17
|
||||
dev: false
|
||||
|
||||
/@solid-primitives/resize-observer@2.0.25(solid-js@1.8.17):
|
||||
/@solid-primitives/resize-observer@2.0.25(solid-js@1.8.18):
|
||||
resolution: {integrity: sha512-jVDXkt2MiriYRaz4DYs62185d+6jQ+1DCsR+v7f6XMsIJJuf963qdBRFjtZtKXBaxdPNMyuPeDgf5XQe3EoDJg==}
|
||||
peerDependencies:
|
||||
solid-js: ^1.6.12
|
||||
dependencies:
|
||||
'@solid-primitives/event-listener': 2.3.3(solid-js@1.8.17)
|
||||
'@solid-primitives/rootless': 1.4.5(solid-js@1.8.17)
|
||||
'@solid-primitives/static-store': 0.0.8(solid-js@1.8.17)
|
||||
'@solid-primitives/utils': 6.2.3(solid-js@1.8.17)
|
||||
solid-js: 1.8.17
|
||||
'@solid-primitives/event-listener': 2.3.3(solid-js@1.8.18)
|
||||
'@solid-primitives/rootless': 1.4.5(solid-js@1.8.18)
|
||||
'@solid-primitives/static-store': 0.0.8(solid-js@1.8.18)
|
||||
'@solid-primitives/utils': 6.2.3(solid-js@1.8.18)
|
||||
solid-js: 1.8.18
|
||||
dev: false
|
||||
|
||||
/@solid-primitives/rootless@1.4.5(solid-js@1.8.17):
|
||||
/@solid-primitives/rootless@1.4.5(solid-js@1.8.18):
|
||||
resolution: {integrity: sha512-GFJE9GC3ojx0aUKqAUZmQPyU8fOVMtnVNrkdk2yS4kd17WqVSpXpoTmo9CnOwA+PG7FTzdIkogvfLQSLs4lrww==}
|
||||
peerDependencies:
|
||||
solid-js: ^1.6.12
|
||||
dependencies:
|
||||
'@solid-primitives/utils': 6.2.3(solid-js@1.8.17)
|
||||
solid-js: 1.8.17
|
||||
'@solid-primitives/utils': 6.2.3(solid-js@1.8.18)
|
||||
solid-js: 1.8.18
|
||||
dev: false
|
||||
|
||||
/@solid-primitives/scheduled@1.4.3(solid-js@1.8.17):
|
||||
resolution: {integrity: sha512-HfWN5w7b7FEc6VPLBKnnE302h90jsLMuR28Fcf7neRGGf8jBj6wm6/UFQ00VlKexHFMR6KQ2u4VBh5a1ZcqM8g==}
|
||||
peerDependencies:
|
||||
solid-js: ^1.6.12
|
||||
dependencies:
|
||||
solid-js: 1.8.17
|
||||
dev: false
|
||||
|
||||
/@solid-primitives/static-store@0.0.8(solid-js@1.8.17):
|
||||
/@solid-primitives/static-store@0.0.8(solid-js@1.8.18):
|
||||
resolution: {integrity: sha512-ZecE4BqY0oBk0YG00nzaAWO5Mjcny8Fc06CdbXadH9T9lzq/9GefqcSe/5AtdXqjvY/DtJ5C6CkcjPZO0o/eqg==}
|
||||
peerDependencies:
|
||||
solid-js: ^1.6.12
|
||||
dependencies:
|
||||
'@solid-primitives/utils': 6.2.3(solid-js@1.8.17)
|
||||
solid-js: 1.8.17
|
||||
'@solid-primitives/utils': 6.2.3(solid-js@1.8.18)
|
||||
solid-js: 1.8.18
|
||||
dev: false
|
||||
|
||||
/@solid-primitives/utils@6.2.3(solid-js@1.8.17):
|
||||
/@solid-primitives/utils@6.2.3(solid-js@1.8.18):
|
||||
resolution: {integrity: sha512-CqAwKb2T5Vi72+rhebSsqNZ9o67buYRdEJrIFzRXz3U59QqezuuxPsyzTSVCacwS5Pf109VRsgCJQoxKRoECZQ==}
|
||||
peerDependencies:
|
||||
solid-js: ^1.6.12
|
||||
dependencies:
|
||||
solid-js: 1.8.17
|
||||
solid-js: 1.8.18
|
||||
dev: false
|
||||
|
||||
/@surma/rollup-plugin-off-main-thread@2.2.3:
|
||||
@@ -2368,11 +2347,11 @@ packages:
|
||||
/@types/node-forge@1.3.11:
|
||||
resolution: {integrity: sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==}
|
||||
dependencies:
|
||||
'@types/node': 20.14.8
|
||||
'@types/node': 20.14.9
|
||||
dev: true
|
||||
|
||||
/@types/node@20.14.8:
|
||||
resolution: {integrity: sha512-DO+2/jZinXfROG7j7WKFn/3C6nFwxy2lLpgLjEXJz+0XKphZlTLJ14mo8Vfg8X5BWN6XjyESXq+LcYdT7tR3bA==}
|
||||
/@types/node@20.14.9:
|
||||
resolution: {integrity: sha512-06OCtnTXtWOZBJlRApleWndH4JsRVs1pDCc8dLSQp+7PpUpX3ePdHyeNSFTeSe7FtKyQkrlPvHwJOW3SLd8Oyg==}
|
||||
dependencies:
|
||||
undici-types: 5.26.5
|
||||
dev: true
|
||||
@@ -2393,7 +2372,7 @@ packages:
|
||||
resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==}
|
||||
requiresBuild: true
|
||||
dependencies:
|
||||
'@types/node': 20.14.8
|
||||
'@types/node': 20.14.9
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
@@ -2406,11 +2385,11 @@ packages:
|
||||
resolution: {integrity: sha512-MxXdReSRhGO7VlFe1bRG/oI7/mdLV9B9JJT0N8vZOhF7gFRR5l3M8W9G8JxmKV+JC5mGqJ0QvqfSOLsCPa4nUw==}
|
||||
engines: {node: '>=0.4.0'}
|
||||
dependencies:
|
||||
acorn: 8.12.0
|
||||
acorn: 8.12.1
|
||||
dev: true
|
||||
|
||||
/acorn@8.12.0:
|
||||
resolution: {integrity: sha512-RTvkC4w+KNXrM39/lWCUaG0IbRkWdCv7W/IOW9oU6SawyxulvkQy5HQPVTKxEjczcUvapcrw3cFx/60VN/NRNw==}
|
||||
/acorn@8.12.1:
|
||||
resolution: {integrity: sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==}
|
||||
engines: {node: '>=0.4.0'}
|
||||
hasBin: true
|
||||
dev: true
|
||||
@@ -2520,7 +2499,7 @@ packages:
|
||||
engines: {node: '>= 4.0.0'}
|
||||
dev: true
|
||||
|
||||
/autoprefixer@10.4.19(postcss@8.4.38):
|
||||
/autoprefixer@10.4.19(postcss@8.4.39):
|
||||
resolution: {integrity: sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew==}
|
||||
engines: {node: ^10 || ^12 || >=14}
|
||||
hasBin: true
|
||||
@@ -2528,11 +2507,11 @@ packages:
|
||||
postcss: ^8.1.0
|
||||
dependencies:
|
||||
browserslist: 4.23.1
|
||||
caniuse-lite: 1.0.30001636
|
||||
caniuse-lite: 1.0.30001640
|
||||
fraction.js: 4.3.7
|
||||
normalize-range: 0.1.2
|
||||
picocolors: 1.0.1
|
||||
postcss: 8.4.38
|
||||
postcss: 8.4.39
|
||||
postcss-value-parser: 4.2.0
|
||||
dev: true
|
||||
|
||||
@@ -2543,8 +2522,8 @@ packages:
|
||||
possible-typed-array-names: 1.0.0
|
||||
dev: true
|
||||
|
||||
/babel-plugin-jsx-dom-expressions@0.37.21(@babel/core@7.24.7):
|
||||
resolution: {integrity: sha512-WbQo1NQ241oki8bYasVzkMXOTSIri5GO/K47rYJb2ZBh8GaPUEWiWbMV3KwXz+96eU2i54N6ThzjQG/f5n8Azw==}
|
||||
/babel-plugin-jsx-dom-expressions@0.37.23(@babel/core@7.24.7):
|
||||
resolution: {integrity: sha512-Y/r8LyLi/njnwPTaDuPEReWk30FJ1KplloYvcFUhHmiH1F7yVVj5mWojD7mbO/IruKyvOs9OIPUoeMi3Z++J4w==}
|
||||
peerDependencies:
|
||||
'@babel/core': ^7.20.12
|
||||
dependencies:
|
||||
@@ -2592,13 +2571,13 @@ packages:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
/babel-preset-solid@1.8.17(@babel/core@7.24.7):
|
||||
resolution: {integrity: sha512-s/FfTZOeds0hYxYqce90Jb+0ycN2lrzC7VP1k1JIn3wBqcaexDKdYi6xjB+hMNkL+Q6HobKbwsriqPloasR9LA==}
|
||||
/babel-preset-solid@1.8.18(@babel/core@7.24.7):
|
||||
resolution: {integrity: sha512-ky0FA4cCS9dk+xYBBItHoxtbRnaDIOGpmHLFqKPaR81hpMbJBOiLOZia2hT0JBwx4zn/D2OjMRvRr6kqtRMoUw==}
|
||||
peerDependencies:
|
||||
'@babel/core': ^7.0.0
|
||||
dependencies:
|
||||
'@babel/core': 7.24.7
|
||||
babel-plugin-jsx-dom-expressions: 0.37.21(@babel/core@7.24.7)
|
||||
babel-plugin-jsx-dom-expressions: 0.37.23(@babel/core@7.24.7)
|
||||
dev: true
|
||||
|
||||
/balanced-match@1.0.2:
|
||||
@@ -2655,10 +2634,10 @@ packages:
|
||||
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
caniuse-lite: 1.0.30001636
|
||||
electron-to-chromium: 1.4.810
|
||||
caniuse-lite: 1.0.30001640
|
||||
electron-to-chromium: 1.4.816
|
||||
node-releases: 2.0.14
|
||||
update-browserslist-db: 1.0.16(browserslist@4.23.1)
|
||||
update-browserslist-db: 1.1.0(browserslist@4.23.1)
|
||||
dev: true
|
||||
|
||||
/buffer-crc32@0.2.13:
|
||||
@@ -2715,8 +2694,8 @@ packages:
|
||||
engines: {node: '>=6'}
|
||||
dev: true
|
||||
|
||||
/caniuse-lite@1.0.30001636:
|
||||
resolution: {integrity: sha512-bMg2vmr8XBsbL6Lr0UHXy/21m84FTxDLWn2FSqMd5PrlbMxwJlQnC2YWYxVgp66PZE+BBNF2jYQUBKCo1FDeZg==}
|
||||
/caniuse-lite@1.0.30001640:
|
||||
resolution: {integrity: sha512-lA4VMpW0PSUrFnkmVuEKBUovSWKhj7puyCg8StBChgu298N1AtuF1sKWEvfDuimSEDbhlb/KqPKC3fs1HbuQUA==}
|
||||
dev: true
|
||||
|
||||
/capnp-ts@0.7.0:
|
||||
@@ -2793,7 +2772,7 @@ packages:
|
||||
engines: {node: '>=12.13.0'}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
'@types/node': 20.14.8
|
||||
'@types/node': 20.14.9
|
||||
escape-string-regexp: 4.0.0
|
||||
is-wsl: 2.2.0
|
||||
lighthouse-logger: 1.4.2
|
||||
@@ -2976,6 +2955,10 @@ packages:
|
||||
is-data-view: 1.0.1
|
||||
dev: true
|
||||
|
||||
/date-fns@3.6.0:
|
||||
resolution: {integrity: sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==}
|
||||
dev: true
|
||||
|
||||
/debug@2.6.9:
|
||||
resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==}
|
||||
peerDependencies:
|
||||
@@ -3118,8 +3101,8 @@ packages:
|
||||
jake: 10.9.1
|
||||
dev: true
|
||||
|
||||
/electron-to-chromium@1.4.810:
|
||||
resolution: {integrity: sha512-Kaxhu4T7SJGpRQx99tq216gCq2nMxJo+uuT6uzz9l8TVN2stL7M06MIIXAtr9jsrLs2Glflgf2vMQRepxawOdQ==}
|
||||
/electron-to-chromium@1.4.816:
|
||||
resolution: {integrity: sha512-EKH5X5oqC6hLmiS7/vYtZHZFTNdhsYG5NVPRN6Yn0kQHNBlT59+xSM8HBy66P5fxWpKgZbPqb+diC64ng295Jw==}
|
||||
dev: true
|
||||
|
||||
/emoji-regex@8.0.0:
|
||||
@@ -3596,7 +3579,7 @@ packages:
|
||||
dependencies:
|
||||
foreground-child: 3.2.1
|
||||
jackspeak: 3.4.0
|
||||
minimatch: 9.0.4
|
||||
minimatch: 9.0.5
|
||||
minipass: 7.1.2
|
||||
package-json-from-dist: 1.0.0
|
||||
path-scurry: 1.11.1
|
||||
@@ -4121,7 +4104,7 @@ packages:
|
||||
engines: {node: '>=14'}
|
||||
dependencies:
|
||||
mlly: 1.7.1
|
||||
pkg-types: 1.1.1
|
||||
pkg-types: 1.1.3
|
||||
dev: true
|
||||
|
||||
/locate-path@5.0.0:
|
||||
@@ -4158,8 +4141,8 @@ packages:
|
||||
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
|
||||
dev: true
|
||||
|
||||
/lru-cache@10.2.2:
|
||||
resolution: {integrity: sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==}
|
||||
/lru-cache@10.3.0:
|
||||
resolution: {integrity: sha512-CQl19J/g+Hbjbv4Y3mFNNXFEL/5t/KCg8POCuUqd4rMKjGG+j1ybER83hxV58zL+dFI1PTkt3GNFSHRt+d8qEQ==}
|
||||
engines: {node: 14 || >=16.14}
|
||||
dev: true
|
||||
|
||||
@@ -4272,21 +4255,21 @@ packages:
|
||||
engines: {node: '>=4'}
|
||||
dev: true
|
||||
|
||||
/miniflare@3.20240610.1:
|
||||
resolution: {integrity: sha512-ZkfSpBmX3nJW00yYhvF2kGvjb6f77TOimRR6+2GQvsArbwo6e0iYqLGM9aB/cnJzgFjLMvOv1qj4756iynSxJQ==}
|
||||
/miniflare@3.20240701.0:
|
||||
resolution: {integrity: sha512-m9+I+7JNyqDGftCMKp9cK9pCZkK72hAL2mM9IWwhct+ZmucLBA8Uu6+rHQqA5iod86cpwOkrB2PrPA3wx9YNgw==}
|
||||
engines: {node: '>=16.13'}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
'@cspotcode/source-map-support': 0.8.1
|
||||
acorn: 8.12.0
|
||||
acorn: 8.12.1
|
||||
acorn-walk: 8.3.3
|
||||
capnp-ts: 0.7.0
|
||||
exit-hook: 2.2.1
|
||||
glob-to-regexp: 0.4.1
|
||||
stoppable: 1.1.0
|
||||
undici: 5.28.4
|
||||
workerd: 1.20240610.1
|
||||
ws: 8.17.1
|
||||
workerd: 1.20240701.0
|
||||
ws: 8.18.0
|
||||
youch: 3.3.3
|
||||
zod: 3.23.8
|
||||
transitivePeerDependencies:
|
||||
@@ -4315,8 +4298,8 @@ packages:
|
||||
brace-expansion: 2.0.1
|
||||
dev: true
|
||||
|
||||
/minimatch@9.0.4:
|
||||
resolution: {integrity: sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==}
|
||||
/minimatch@9.0.5:
|
||||
resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==}
|
||||
engines: {node: '>=16 || 14 >=14.17'}
|
||||
dependencies:
|
||||
brace-expansion: 2.0.1
|
||||
@@ -4343,9 +4326,9 @@ packages:
|
||||
/mlly@1.7.1:
|
||||
resolution: {integrity: sha512-rrVRZRELyQzrIUAVMHxP97kv+G786pHmOKzuFII8zDYahFBS7qnHh2AlYSl1GAHhaMPCz6/oHjVMcfFYgFYHgA==}
|
||||
dependencies:
|
||||
acorn: 8.12.0
|
||||
acorn: 8.12.1
|
||||
pathe: 1.1.2
|
||||
pkg-types: 1.1.1
|
||||
pkg-types: 1.1.3
|
||||
ufo: 1.5.3
|
||||
dev: true
|
||||
|
||||
@@ -4586,7 +4569,7 @@ packages:
|
||||
resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==}
|
||||
engines: {node: '>=16 || 14 >=14.18'}
|
||||
dependencies:
|
||||
lru-cache: 10.2.2
|
||||
lru-cache: 10.3.0
|
||||
minipass: 7.1.2
|
||||
dev: true
|
||||
|
||||
@@ -4628,8 +4611,8 @@ packages:
|
||||
find-up: 4.1.0
|
||||
dev: true
|
||||
|
||||
/pkg-types@1.1.1:
|
||||
resolution: {integrity: sha512-ko14TjmDuQJ14zsotODv7dBlwxKhUKQEhuhmbqo1uCi9BB0Z2alo/wAXg6q1dTR5TyuqYyWhjtfe/Tsh+X28jQ==}
|
||||
/pkg-types@1.1.3:
|
||||
resolution: {integrity: sha512-+JrgthZG6m3ckicaOB74TwQ+tBWsFl3qVQg7mN8ulwSOElJ7gBhKzj2VkCPnZ4NlF6kEquYU+RIYNVAvzd54UA==}
|
||||
dependencies:
|
||||
confbox: 0.1.7
|
||||
mlly: 1.7.1
|
||||
@@ -4641,29 +4624,29 @@ packages:
|
||||
engines: {node: '>= 0.4'}
|
||||
dev: true
|
||||
|
||||
/postcss-import@15.1.0(postcss@8.4.38):
|
||||
/postcss-import@15.1.0(postcss@8.4.39):
|
||||
resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
peerDependencies:
|
||||
postcss: ^8.0.0
|
||||
dependencies:
|
||||
postcss: 8.4.38
|
||||
postcss: 8.4.39
|
||||
postcss-value-parser: 4.2.0
|
||||
read-cache: 1.0.0
|
||||
resolve: 1.22.8
|
||||
dev: true
|
||||
|
||||
/postcss-js@4.0.1(postcss@8.4.38):
|
||||
/postcss-js@4.0.1(postcss@8.4.39):
|
||||
resolution: {integrity: sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==}
|
||||
engines: {node: ^12 || ^14 || >= 16}
|
||||
peerDependencies:
|
||||
postcss: ^8.4.21
|
||||
dependencies:
|
||||
camelcase-css: 2.0.1
|
||||
postcss: 8.4.38
|
||||
postcss: 8.4.39
|
||||
dev: true
|
||||
|
||||
/postcss-load-config@4.0.2(postcss@8.4.38):
|
||||
/postcss-load-config@4.0.2(postcss@8.4.39):
|
||||
resolution: {integrity: sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==}
|
||||
engines: {node: '>= 14'}
|
||||
peerDependencies:
|
||||
@@ -4676,17 +4659,17 @@ packages:
|
||||
optional: true
|
||||
dependencies:
|
||||
lilconfig: 3.1.2
|
||||
postcss: 8.4.38
|
||||
postcss: 8.4.39
|
||||
yaml: 2.4.5
|
||||
dev: true
|
||||
|
||||
/postcss-nested@6.0.1(postcss@8.4.38):
|
||||
/postcss-nested@6.0.1(postcss@8.4.39):
|
||||
resolution: {integrity: sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==}
|
||||
engines: {node: '>=12.0'}
|
||||
peerDependencies:
|
||||
postcss: ^8.2.14
|
||||
dependencies:
|
||||
postcss: 8.4.38
|
||||
postcss: 8.4.39
|
||||
postcss-selector-parser: 6.1.0
|
||||
dev: true
|
||||
|
||||
@@ -4702,8 +4685,8 @@ packages:
|
||||
resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==}
|
||||
dev: true
|
||||
|
||||
/postcss@8.4.38:
|
||||
resolution: {integrity: sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==}
|
||||
/postcss@8.4.39:
|
||||
resolution: {integrity: sha512-0vzE+lAiG7hZl1/9I8yzKLx3aR9Xbof3fBHKunvMfOCYAtMhrsnccJY2iTURb9EZd5+pLuiNV9/c/GZJOHsgIw==}
|
||||
engines: {node: ^10 || ^12 || >=14}
|
||||
dependencies:
|
||||
nanoid: 3.3.7
|
||||
@@ -4711,7 +4694,7 @@ packages:
|
||||
source-map-js: 1.2.0
|
||||
dev: true
|
||||
|
||||
/prettier-plugin-tailwindcss@0.6.5(@ianvs/prettier-plugin-sort-imports@4.2.1)(prettier@3.3.2):
|
||||
/prettier-plugin-tailwindcss@0.6.5(@ianvs/prettier-plugin-sort-imports@4.3.0)(prettier@3.3.2):
|
||||
resolution: {integrity: sha512-axfeOArc/RiGHjOIy9HytehlC0ZLeMaqY09mm8YCkMzznKiDkwFzOpBvtuhuv3xG5qB73+Mj7OCe2j/L1ryfuQ==}
|
||||
engines: {node: '>=14.21.3'}
|
||||
peerDependencies:
|
||||
@@ -4763,7 +4746,7 @@ packages:
|
||||
prettier-plugin-svelte:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@ianvs/prettier-plugin-sort-imports': 4.2.1(prettier@3.3.2)
|
||||
'@ianvs/prettier-plugin-sort-imports': 4.3.0(prettier@3.3.2)
|
||||
prettier: 3.3.2
|
||||
dev: true
|
||||
|
||||
@@ -5228,14 +5211,14 @@ packages:
|
||||
resolution: {integrity: sha512-g6T+p7QO8npa+/hNx9ohv1E5pVCmWrVCUzUXJyLdMmftX6ER0oiWY/w9knEonLpnOp6b6FenKnMfR8gqwWdwig==}
|
||||
dev: true
|
||||
|
||||
/solid-js@1.8.17:
|
||||
resolution: {integrity: sha512-E0FkUgv9sG/gEBWkHr/2XkBluHb1fkrHywUgA6o6XolPDCJ4g1HaLmQufcBBhiF36ee40q+HpG/vCZu7fLpI3Q==}
|
||||
/solid-js@1.8.18:
|
||||
resolution: {integrity: sha512-cpkxDPvO/AuKBugVv6xKFd1C9VC0XZMu4VtF56IlHoux8HgyW44uqNSWbozMnVcpIzHIhS3vVXPAVZYM26jpWw==}
|
||||
dependencies:
|
||||
csstype: 3.1.3
|
||||
seroval: 1.0.7
|
||||
seroval-plugins: 1.0.7(seroval@1.0.7)
|
||||
|
||||
/solid-refresh@0.6.3(solid-js@1.8.17):
|
||||
/solid-refresh@0.6.3(solid-js@1.8.18):
|
||||
resolution: {integrity: sha512-F3aPsX6hVw9ttm5LYlth8Q15x6MlI/J3Dn+o3EQyRTtTxidepSTwAYdozt01/YA+7ObcciagGEyXIopGZzQtbA==}
|
||||
peerDependencies:
|
||||
solid-js: ^1.3
|
||||
@@ -5243,7 +5226,7 @@ packages:
|
||||
'@babel/generator': 7.24.7
|
||||
'@babel/helper-module-imports': 7.24.7
|
||||
'@babel/types': 7.24.7
|
||||
solid-js: 1.8.17
|
||||
solid-js: 1.8.18
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: true
|
||||
@@ -5488,11 +5471,11 @@ packages:
|
||||
normalize-path: 3.0.0
|
||||
object-hash: 3.0.0
|
||||
picocolors: 1.0.1
|
||||
postcss: 8.4.38
|
||||
postcss-import: 15.1.0(postcss@8.4.38)
|
||||
postcss-js: 4.0.1(postcss@8.4.38)
|
||||
postcss-load-config: 4.0.2(postcss@8.4.38)
|
||||
postcss-nested: 6.0.1(postcss@8.4.38)
|
||||
postcss: 8.4.39
|
||||
postcss-import: 15.1.0(postcss@8.4.39)
|
||||
postcss-js: 4.0.1(postcss@8.4.39)
|
||||
postcss-load-config: 4.0.2(postcss@8.4.39)
|
||||
postcss-nested: 6.0.1(postcss@8.4.39)
|
||||
postcss-selector-parser: 6.1.0
|
||||
resolve: 1.22.8
|
||||
sucrase: 3.35.0
|
||||
@@ -5541,7 +5524,7 @@ packages:
|
||||
hasBin: true
|
||||
dependencies:
|
||||
'@jridgewell/source-map': 0.3.6
|
||||
acorn: 8.12.0
|
||||
acorn: 8.12.1
|
||||
commander: 2.20.3
|
||||
source-map-support: 0.5.21
|
||||
dev: true
|
||||
@@ -5667,8 +5650,8 @@ packages:
|
||||
possible-typed-array-names: 1.0.0
|
||||
dev: true
|
||||
|
||||
/typescript@5.5.2:
|
||||
resolution: {integrity: sha512-NcRtPEOsPFFWjobJEtfihkLCZCXZt/os3zf8nTxjVH3RvTSxjrCamJpbExGvYOF+tFHc3pA65qpdwPbzjohhew==}
|
||||
/typescript@5.5.3:
|
||||
resolution: {integrity: sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ==}
|
||||
engines: {node: '>=14.17'}
|
||||
hasBin: true
|
||||
dev: true
|
||||
@@ -5742,7 +5725,7 @@ packages:
|
||||
resolution: {integrity: sha512-91mxcZTadgXyj3lFWmrGT8GyoRHWuE5fqPOjg5RVtF6vj+OfM5G6WCzXjuYtSgELE5ggB34RY4oiCSEP8I3AHw==}
|
||||
dependencies:
|
||||
'@rollup/pluginutils': 5.1.0(rollup@2.79.1)
|
||||
acorn: 8.12.0
|
||||
acorn: 8.12.1
|
||||
escape-string-regexp: 5.0.0
|
||||
estree-walker: 3.0.3
|
||||
fast-glob: 3.3.2
|
||||
@@ -5750,10 +5733,10 @@ packages:
|
||||
magic-string: 0.30.10
|
||||
mlly: 1.7.1
|
||||
pathe: 1.1.2
|
||||
pkg-types: 1.1.1
|
||||
pkg-types: 1.1.3
|
||||
scule: 1.3.0
|
||||
strip-literal: 2.1.0
|
||||
unplugin: 1.10.1
|
||||
unplugin: 1.11.0
|
||||
transitivePeerDependencies:
|
||||
- rollup
|
||||
dev: true
|
||||
@@ -5782,14 +5765,14 @@ packages:
|
||||
'@vueuse/core':
|
||||
optional: true
|
||||
dependencies:
|
||||
'@antfu/utils': 0.7.8
|
||||
'@antfu/utils': 0.7.10
|
||||
'@rollup/pluginutils': 5.1.0(rollup@2.79.1)
|
||||
fast-glob: 3.3.2
|
||||
local-pkg: 0.5.0
|
||||
magic-string: 0.30.10
|
||||
minimatch: 9.0.4
|
||||
minimatch: 9.0.5
|
||||
unimport: 3.7.2(rollup@2.79.1)
|
||||
unplugin: 1.10.1
|
||||
unplugin: 1.11.0
|
||||
transitivePeerDependencies:
|
||||
- rollup
|
||||
dev: true
|
||||
@@ -5815,21 +5798,21 @@ packages:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@antfu/install-pkg': 0.3.3
|
||||
'@antfu/utils': 0.7.8
|
||||
'@antfu/utils': 0.7.10
|
||||
'@iconify/utils': 2.1.25
|
||||
debug: 4.3.5
|
||||
kolorist: 1.8.0
|
||||
local-pkg: 0.5.0
|
||||
unplugin: 1.10.1
|
||||
unplugin: 1.11.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
/unplugin@1.10.1:
|
||||
resolution: {integrity: sha512-d6Mhq8RJeGA8UfKCu54Um4lFA0eSaRa3XxdAJg8tIdxbu1ubW0hBCZUL7yI2uGyYCRndvbK8FLHzqy2XKfeMsg==}
|
||||
/unplugin@1.11.0:
|
||||
resolution: {integrity: sha512-3r7VWZ/webh0SGgJScpWl2/MRCZK5d3ZYFcNaeci/GQ7Teop7zf0Nl2pUuz7G21BwPd9pcUPOC5KmJ2L3WgC5g==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
dependencies:
|
||||
acorn: 8.12.0
|
||||
acorn: 8.12.1
|
||||
chokidar: 3.6.0
|
||||
webpack-sources: 3.2.3
|
||||
webpack-virtual-modules: 0.6.2
|
||||
@@ -5840,8 +5823,8 @@ packages:
|
||||
engines: {node: '>=4'}
|
||||
dev: true
|
||||
|
||||
/update-browserslist-db@1.0.16(browserslist@4.23.1):
|
||||
resolution: {integrity: sha512-KVbTxlBYlckhF5wgfyZXTWnMn7MMZjMu9XG8bPlliUOP9ThaF4QnhP8qrjrH7DRzHfSk0oQv1wToW+iA5GajEQ==}
|
||||
/update-browserslist-db@1.1.0(browserslist@4.23.1):
|
||||
resolution: {integrity: sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
browserslist: '>= 4.21.0'
|
||||
@@ -5872,7 +5855,7 @@ packages:
|
||||
spdx-expression-parse: 3.0.1
|
||||
dev: true
|
||||
|
||||
/vite-plugin-pwa@0.20.0(vite@5.3.1)(workbox-build@7.1.1)(workbox-window@7.1.0):
|
||||
/vite-plugin-pwa@0.20.0(vite@5.3.3)(workbox-build@7.1.1)(workbox-window@7.1.0):
|
||||
resolution: {integrity: sha512-/kDZyqF8KqoXRpMUQtR5Atri/7BWayW8Gp7Kz/4bfstsV6zSFTxjREbXZYL7zSuRL40HGA+o2hvUAFRmC+bL7g==}
|
||||
engines: {node: '>=16.0.0'}
|
||||
peerDependencies:
|
||||
@@ -5887,14 +5870,14 @@ packages:
|
||||
debug: 4.3.5
|
||||
fast-glob: 3.3.2
|
||||
pretty-bytes: 6.1.1
|
||||
vite: 5.3.1
|
||||
vite: 5.3.3
|
||||
workbox-build: 7.1.1
|
||||
workbox-window: 7.1.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
/vite-plugin-solid@2.10.2(solid-js@1.8.17)(vite@5.3.1):
|
||||
/vite-plugin-solid@2.10.2(solid-js@1.8.18)(vite@5.3.3):
|
||||
resolution: {integrity: sha512-AOEtwMe2baBSXMXdo+BUwECC8IFHcKS6WQV/1NEd+Q7vHPap5fmIhLcAzr+DUJ04/KHx/1UBU0l1/GWP+rMAPQ==}
|
||||
peerDependencies:
|
||||
'@testing-library/jest-dom': ^5.16.6 || ^5.17.0 || ^6.*
|
||||
@@ -5906,18 +5889,18 @@ packages:
|
||||
dependencies:
|
||||
'@babel/core': 7.24.7
|
||||
'@types/babel__core': 7.20.5
|
||||
babel-preset-solid: 1.8.17(@babel/core@7.24.7)
|
||||
babel-preset-solid: 1.8.18(@babel/core@7.24.7)
|
||||
merge-anything: 5.1.7
|
||||
solid-js: 1.8.17
|
||||
solid-refresh: 0.6.3(solid-js@1.8.17)
|
||||
vite: 5.3.1
|
||||
vitefu: 0.2.5(vite@5.3.1)
|
||||
solid-js: 1.8.18
|
||||
solid-refresh: 0.6.3(solid-js@1.8.18)
|
||||
vite: 5.3.3
|
||||
vitefu: 0.2.5(vite@5.3.3)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
/vite@5.3.1:
|
||||
resolution: {integrity: sha512-XBmSKRLXLxiaPYamLv3/hnP/KXDai1NDexN0FpkTaZXTfycHvkRHoenpgl/fvuK/kPbB6xAgoyiryAhQNxYmAQ==}
|
||||
/vite@5.3.3:
|
||||
resolution: {integrity: sha512-NPQdeCU0Dv2z5fu+ULotpuq5yfCS1BzKUIPhNbP3YBfAMGJXbt2nS+sbTFu+qchaqWTD+H3JK++nRwr6XIcp6A==}
|
||||
engines: {node: ^18.0.0 || >=20.0.0}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
@@ -5945,13 +5928,13 @@ packages:
|
||||
optional: true
|
||||
dependencies:
|
||||
esbuild: 0.21.5
|
||||
postcss: 8.4.38
|
||||
postcss: 8.4.39
|
||||
rollup: 4.18.0
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.3
|
||||
dev: true
|
||||
|
||||
/vitefu@0.2.5(vite@5.3.1):
|
||||
/vitefu@0.2.5(vite@5.3.3):
|
||||
resolution: {integrity: sha512-SgHtMLoqaeeGnd2evZ849ZbACbnwQCIwRH57t18FxcXoZop0uQu0uzlIhJBlF/eWVzuce0sHeqPcDo+evVcg8Q==}
|
||||
peerDependencies:
|
||||
vite: ^3.0.0 || ^4.0.0 || ^5.0.0
|
||||
@@ -5959,7 +5942,7 @@ packages:
|
||||
vite:
|
||||
optional: true
|
||||
dependencies:
|
||||
vite: 5.3.1
|
||||
vite: 5.3.3
|
||||
dev: true
|
||||
|
||||
/webidl-conversions@3.0.1:
|
||||
@@ -6169,36 +6152,37 @@ packages:
|
||||
workbox-core: 7.1.0
|
||||
dev: true
|
||||
|
||||
/workerd@1.20240610.1:
|
||||
resolution: {integrity: sha512-Rtut5GrsODQMh6YU43b9WZ980Wd05Ov1/ds88pT/SoetmXFBvkBzdRfiHiATv+azmGX8KveE0i/Eqzk/yI01ug==}
|
||||
/workerd@1.20240701.0:
|
||||
resolution: {integrity: sha512-qSgNVqauqzNCij9MaJLF2c2ko3AnFioVSIxMSryGbRK+LvtGr9BKBt6JOxCb24DoJASoJDx3pe3DJHBVydUiBg==}
|
||||
engines: {node: '>=16'}
|
||||
hasBin: true
|
||||
requiresBuild: true
|
||||
optionalDependencies:
|
||||
'@cloudflare/workerd-darwin-64': 1.20240610.1
|
||||
'@cloudflare/workerd-darwin-arm64': 1.20240610.1
|
||||
'@cloudflare/workerd-linux-64': 1.20240610.1
|
||||
'@cloudflare/workerd-linux-arm64': 1.20240610.1
|
||||
'@cloudflare/workerd-windows-64': 1.20240610.1
|
||||
'@cloudflare/workerd-darwin-64': 1.20240701.0
|
||||
'@cloudflare/workerd-darwin-arm64': 1.20240701.0
|
||||
'@cloudflare/workerd-linux-64': 1.20240701.0
|
||||
'@cloudflare/workerd-linux-arm64': 1.20240701.0
|
||||
'@cloudflare/workerd-windows-64': 1.20240701.0
|
||||
dev: true
|
||||
|
||||
/wrangler@3.61.0:
|
||||
resolution: {integrity: sha512-feVAp0986x9xL3Dc1zin0ZVXKaqzp7eZur7iPLnpEwjG1Xy4dkVEZ5a1LET94Iyejt1P+EX5lgGcz63H7EfzUw==}
|
||||
/wrangler@3.63.1:
|
||||
resolution: {integrity: sha512-fxMPNEyDc9pZNtQOuYqRikzv6lL5eP4S1zv7L/kw24uu1cCEmJ39j8bfJGzrAEqKDNsiFXVjEka0RjlpgEVWPg==}
|
||||
engines: {node: '>=16.17.0'}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
'@cloudflare/workers-types': ^4.20240605.0
|
||||
'@cloudflare/workers-types': ^4.20240620.0
|
||||
peerDependenciesMeta:
|
||||
'@cloudflare/workers-types':
|
||||
optional: true
|
||||
dependencies:
|
||||
'@cloudflare/kv-asset-handler': 0.3.3
|
||||
'@cloudflare/kv-asset-handler': 0.3.4
|
||||
'@esbuild-plugins/node-globals-polyfill': 0.2.3(esbuild@0.17.19)
|
||||
'@esbuild-plugins/node-modules-polyfill': 0.2.2(esbuild@0.17.19)
|
||||
blake3-wasm: 2.1.5
|
||||
chokidar: 3.6.0
|
||||
date-fns: 3.6.0
|
||||
esbuild: 0.17.19
|
||||
miniflare: 3.20240610.1
|
||||
miniflare: 3.20240701.0
|
||||
nanoid: 3.3.7
|
||||
path-to-regexp: 6.2.2
|
||||
resolve: 1.22.8
|
||||
@@ -6237,8 +6221,8 @@ packages:
|
||||
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
|
||||
dev: true
|
||||
|
||||
/ws@8.17.1:
|
||||
resolution: {integrity: sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==}
|
||||
/ws@8.18.0:
|
||||
resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
peerDependencies:
|
||||
bufferutil: ^4.0.1
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "Satonomics",
|
||||
"short_name": "Satonomics",
|
||||
"description": "Satoshi Economics",
|
||||
"description": "A better, FOSS, Bitcoin-only, self-hostable Glassnode",
|
||||
"start_url": "/",
|
||||
"display": "standalone",
|
||||
"theme_color": "#0c0a09",
|
||||
|
||||
@@ -4,6 +4,8 @@ const texts = [
|
||||
"satonomics",
|
||||
"satonomics",
|
||||
"satonomics",
|
||||
"satonomics",
|
||||
"satonomics",
|
||||
|
||||
"stay humble, stack sats",
|
||||
"21 million",
|
||||
@@ -13,7 +15,7 @@ const texts = [
|
||||
"hodl",
|
||||
`don't trust, verify`,
|
||||
"zap",
|
||||
"bitcoin",
|
||||
"₿itcoin",
|
||||
"lightning",
|
||||
"nostr",
|
||||
"freedom tech",
|
||||
@@ -29,52 +31,60 @@ const texts = [
|
||||
"be your own bank",
|
||||
"resistance money",
|
||||
"foss",
|
||||
"permissionless",
|
||||
"great reset",
|
||||
"orange pill",
|
||||
"borderless",
|
||||
"anonymous",
|
||||
"nyknyc",
|
||||
"low time preference",
|
||||
"absolute scarcity",
|
||||
"time is scarce",
|
||||
"ride or die",
|
||||
"cyberpunk",
|
||||
];
|
||||
|
||||
export const LOCAL_STORAGE_MARQUEE_KEY = "bg-marquee";
|
||||
|
||||
export function Background({
|
||||
marquee: on,
|
||||
mode,
|
||||
opacity,
|
||||
focused,
|
||||
}: {
|
||||
marquee: Accessor<boolean>;
|
||||
mode: SL<"Scroll" | "Static">;
|
||||
opacity: SL<{ text: string; value: number }>;
|
||||
focused: Accessor<boolean>;
|
||||
}) {
|
||||
createEffect(() => {
|
||||
if (on()) {
|
||||
localStorage.removeItem(LOCAL_STORAGE_MARQUEE_KEY);
|
||||
} else {
|
||||
localStorage.setItem(LOCAL_STORAGE_MARQUEE_KEY, "false");
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<div class="absolute h-full w-full overflow-hidden opacity-[0.0333] will-change-auto">
|
||||
<div
|
||||
class="absolute h-full w-full overflow-hidden will-change-auto"
|
||||
style={{
|
||||
opacity: opacity.selected().value,
|
||||
}}
|
||||
>
|
||||
<div class="-m-[2rem] -space-y-1 overflow-hidden md:-m-[1rem]">
|
||||
<Line on={on} focused={focused} />
|
||||
<Line on={on} focused={focused} />
|
||||
<Line on={on} focused={focused} />
|
||||
<Line on={on} focused={focused} />
|
||||
<Line on={on} focused={focused} />
|
||||
<Line on={on} focused={focused} />
|
||||
<Line on={on} focused={focused} />
|
||||
<Line on={on} focused={focused} />
|
||||
<Line on={on} focused={focused} />
|
||||
<Line on={on} focused={focused} />
|
||||
<Line on={on} focused={focused} />
|
||||
<Line on={on} focused={focused} />
|
||||
<Line on={on} focused={focused} />
|
||||
<Line on={on} focused={focused} />
|
||||
<Line on={on} focused={focused} />
|
||||
<Line on={on} focused={focused} />
|
||||
<Line on={on} focused={focused} />
|
||||
<Line on={on} focused={focused} />
|
||||
<Line on={on} focused={focused} />
|
||||
<Line on={on} focused={focused} />
|
||||
<Line on={on} focused={focused} />
|
||||
<Line on={on} focused={focused} />
|
||||
<Line on={on} focused={focused} />
|
||||
<Line mode={mode} focused={focused} />
|
||||
<Line mode={mode} focused={focused} />
|
||||
<Line mode={mode} focused={focused} />
|
||||
<Line mode={mode} focused={focused} />
|
||||
<Line mode={mode} focused={focused} />
|
||||
<Line mode={mode} focused={focused} />
|
||||
<Line mode={mode} focused={focused} />
|
||||
<Line mode={mode} focused={focused} />
|
||||
<Line mode={mode} focused={focused} />
|
||||
<Line mode={mode} focused={focused} />
|
||||
<Line mode={mode} focused={focused} />
|
||||
<Line mode={mode} focused={focused} />
|
||||
<Line mode={mode} focused={focused} />
|
||||
<Line mode={mode} focused={focused} />
|
||||
<Line mode={mode} focused={focused} />
|
||||
<Line mode={mode} focused={focused} />
|
||||
<Line mode={mode} focused={focused} />
|
||||
<Line mode={mode} focused={focused} />
|
||||
<Line mode={mode} focused={focused} />
|
||||
<Line mode={mode} focused={focused} />
|
||||
<Line mode={mode} focused={focused} />
|
||||
<Line mode={mode} focused={focused} />
|
||||
<Line mode={mode} focused={focused} />
|
||||
</div>
|
||||
</div>
|
||||
<div class="absolute h-full w-full opacity-10 mix-blend-multiply">
|
||||
@@ -88,10 +98,10 @@ export function Background({
|
||||
}
|
||||
|
||||
function Line({
|
||||
on,
|
||||
mode,
|
||||
focused,
|
||||
}: {
|
||||
on: Accessor<boolean>;
|
||||
mode: SL<"Scroll" | "Static">;
|
||||
focused: Accessor<boolean>;
|
||||
}) {
|
||||
const shuffled = shuffle([...texts]);
|
||||
@@ -100,17 +110,17 @@ function Line({
|
||||
|
||||
return (
|
||||
<div class="select-none whitespace-nowrap">
|
||||
<TextWrapper on={on} focused={focused} joined={joined} />
|
||||
<TextWrapper mode={mode} focused={focused} joined={joined} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function TextWrapper({
|
||||
joined,
|
||||
on,
|
||||
mode,
|
||||
focused,
|
||||
}: {
|
||||
on: Accessor<boolean>;
|
||||
mode: SL<"Scroll" | "Static">;
|
||||
focused: Accessor<boolean>;
|
||||
joined: string;
|
||||
}) {
|
||||
@@ -119,7 +129,7 @@ function TextWrapper({
|
||||
const wasOnceOn = createRWS(false);
|
||||
|
||||
createEffect(() => {
|
||||
if (!wasOnceOn() && on()) {
|
||||
if (!wasOnceOn() && mode.selected() === "Scroll") {
|
||||
wasOnceOn.set(true);
|
||||
}
|
||||
});
|
||||
@@ -131,7 +141,10 @@ function TextWrapper({
|
||||
...(wasOnceOn()
|
||||
? {
|
||||
animation: `marquee ${seconds}s linear infinite`,
|
||||
"animation-play-state": focused() && on() ? "running" : "paused",
|
||||
"animation-play-state":
|
||||
focused() && mode.selected() === "Scroll"
|
||||
? "running"
|
||||
: "paused",
|
||||
}
|
||||
: {}),
|
||||
}}
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
import { createResizeObserver } from "@solid-primitives/resize-observer";
|
||||
|
||||
import { classPropToString } from "/src/solid/classes";
|
||||
import { createRWS } from "/src/solid/rws";
|
||||
|
||||
export function Box({
|
||||
flex = true,
|
||||
@@ -9,43 +6,14 @@ export function Box({
|
||||
padded = true,
|
||||
children,
|
||||
dark,
|
||||
overflowY,
|
||||
classes,
|
||||
}: {
|
||||
flex?: boolean;
|
||||
absolute?: "top" | "bottom";
|
||||
padded?: boolean;
|
||||
dark?: boolean;
|
||||
overflowY?: boolean;
|
||||
classes?: string;
|
||||
} & ParentProps) {
|
||||
const maybeScrollable = createRWS<HTMLDivElement | undefined>(undefined);
|
||||
const scrollable = createRWS(false);
|
||||
const showLeftArrow = createRWS(false);
|
||||
const showRightArrow = createRWS(false);
|
||||
|
||||
onMount(() => {
|
||||
createResizeObserver(maybeScrollable, (_, el) => {
|
||||
if (el !== maybeScrollable()) {
|
||||
return;
|
||||
}
|
||||
|
||||
scrollable.set(() => el.scrollWidth > el.clientWidth);
|
||||
|
||||
checkArrows();
|
||||
});
|
||||
});
|
||||
|
||||
function checkArrows() {
|
||||
const offset = 20;
|
||||
|
||||
const target = maybeScrollable()!;
|
||||
|
||||
const left = target.scrollLeft;
|
||||
const right = target.scrollWidth - target.scrollLeft - target.clientWidth;
|
||||
|
||||
showLeftArrow.set(() => left > offset);
|
||||
showRightArrow.set(() => right > offset);
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
class={classPropToString([
|
||||
@@ -55,94 +23,23 @@ export function Box({
|
||||
"absolute inset-x-0",
|
||||
absolute === "top"
|
||||
? "top-0"
|
||||
: "pointer-events-none bottom-0 bg-gradient-to-b from-transparent to-black",
|
||||
: "pointer-events-none bottom-0 bg-gradient-to-b from-transparent to-orange-100 dark:to-black",
|
||||
]
|
||||
: "relative",
|
||||
classes,
|
||||
])}
|
||||
>
|
||||
<div
|
||||
class={classPropToString([
|
||||
"pointer-events-auto relative overflow-hidden rounded-xl border border-orange-200/10 shadow-md",
|
||||
"border-lighter pointer-events-auto relative overflow-hidden rounded-xl border shadow-md",
|
||||
dark
|
||||
? "bg-orange-100/5 backdrop-blur-sm"
|
||||
: "bg-orange-200/10 backdrop-blur-md",
|
||||
? "bg-white/40 backdrop-blur-sm dark:bg-orange-100/5"
|
||||
: "bg-white/60 backdrop-blur-md dark:bg-orange-200/10",
|
||||
])}
|
||||
>
|
||||
<For
|
||||
each={[
|
||||
{
|
||||
showArrow: showLeftArrow,
|
||||
side: "left-0",
|
||||
order: "",
|
||||
buttonPadding: "pl-3 pr-2",
|
||||
iconPadding: "pr-0.5",
|
||||
scrollMultiplier: -1,
|
||||
chevronIcon: IconTablerChevronLeft,
|
||||
gradientDirection: "bg-gradient-to-r",
|
||||
},
|
||||
{
|
||||
showArrow: showRightArrow,
|
||||
side: "right-0",
|
||||
order: "order-2",
|
||||
buttonPadding: "pl-2 pr-3",
|
||||
iconPadding: "pl-0.5",
|
||||
scrollMultiplier: 1,
|
||||
chevronIcon: IconTablerChevronRight,
|
||||
gradientDirection: "bg-gradient-to-l",
|
||||
},
|
||||
]}
|
||||
>
|
||||
{(obj) => (
|
||||
<Show when={scrollable() && obj.showArrow()}>
|
||||
<div
|
||||
class={[
|
||||
obj.side,
|
||||
"pointer-events-none absolute bottom-0 top-0 z-20 flex transition-opacity duration-200 ease-in-out",
|
||||
].join(" ")}
|
||||
>
|
||||
<div
|
||||
class={[
|
||||
obj.order,
|
||||
obj.buttonPadding,
|
||||
"pointer-events-auto hidden h-full items-center bg-black/90 md:flex",
|
||||
].join(" ")}
|
||||
>
|
||||
<button
|
||||
onClick={() => {
|
||||
maybeScrollable()?.scrollBy({
|
||||
left: Math.floor(
|
||||
maybeScrollable()!.clientWidth *
|
||||
obj.scrollMultiplier *
|
||||
0.8,
|
||||
),
|
||||
behavior: "smooth",
|
||||
});
|
||||
}}
|
||||
class="rounded-full border border-orange-200/20 bg-black p-0.5 transition hover:scale-110 active:scale-100"
|
||||
>
|
||||
<Dynamic
|
||||
component={obj.chevronIcon}
|
||||
class={[`size-5 ${obj.iconPadding}`]}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class={[
|
||||
obj.gradientDirection,
|
||||
"h-full w-10 from-black/90 to-transparent",
|
||||
].join(" ")}
|
||||
/>
|
||||
</div>
|
||||
</Show>
|
||||
)}
|
||||
</For>
|
||||
|
||||
<div
|
||||
ref={maybeScrollable.set}
|
||||
onScroll={checkArrows}
|
||||
class={classPropToString([
|
||||
flex && "flex w-full space-x-2",
|
||||
overflowY && "overflow-y-auto",
|
||||
padded && "p-1.5",
|
||||
])}
|
||||
>
|
||||
|
||||
@@ -1,9 +1,4 @@
|
||||
import { generate } from "lean-qr";
|
||||
|
||||
import { chartState } from "/src/scripts/lightweightCharts/chart/state";
|
||||
import { setTimeScale } from "/src/scripts/lightweightCharts/chart/time";
|
||||
import { classPropToString } from "/src/solid/classes";
|
||||
import { createRWS } from "/src/solid/rws";
|
||||
import { Button } from "./button";
|
||||
|
||||
export function Actions({
|
||||
presets,
|
||||
@@ -14,8 +9,12 @@ export function Actions({
|
||||
qrcode: RWS<string>;
|
||||
fullscreen?: RWS<boolean>;
|
||||
}) {
|
||||
const ButtonShare = lazy(() =>
|
||||
import("./buttonShare").then((d) => ({ default: d.ButtonShare })),
|
||||
);
|
||||
|
||||
return (
|
||||
<div class="flex space-x-1">
|
||||
<div class="flex space-x-1 p-1.5">
|
||||
<Show when={fullscreen}>
|
||||
{(fullscreen) => (
|
||||
<Button
|
||||
@@ -26,29 +25,15 @@ export function Actions({
|
||||
: IconTablerLayoutSidebarRightExpand
|
||||
}
|
||||
onClick={() => {
|
||||
const range = chartState.range;
|
||||
|
||||
fullscreen().set((b) => !b);
|
||||
|
||||
setTimeScale(range);
|
||||
}}
|
||||
classes="hidden md:block"
|
||||
/>
|
||||
)}
|
||||
</Show>
|
||||
|
||||
<Button
|
||||
title="Share"
|
||||
icon={() => IconTablerShare}
|
||||
onClick={() => {
|
||||
qrcode.set(() =>
|
||||
generate(document.location.href).toDataURL({
|
||||
on: [0xff, 0xff, 0xff, 0xff],
|
||||
off: [0x00, 0x00, 0x00, 0x00],
|
||||
}),
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<ButtonShare qrcode={qrcode} />
|
||||
|
||||
<Button
|
||||
title="Favorite"
|
||||
colors={() =>
|
||||
@@ -66,38 +51,3 @@ export function Actions({
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Button({
|
||||
title,
|
||||
icon,
|
||||
colors,
|
||||
onClick,
|
||||
disabled,
|
||||
classes,
|
||||
}: {
|
||||
title: string;
|
||||
icon: () => ValidComponent;
|
||||
colors?: () => string;
|
||||
onClick: VoidFunction;
|
||||
disabled?: () => boolean;
|
||||
classes?: string;
|
||||
}) {
|
||||
return (
|
||||
<button
|
||||
title={title}
|
||||
disabled={disabled?.()}
|
||||
class={classPropToString([
|
||||
colors?.() || (disabled?.() ? "" : "hover:bg-orange-200/15"),
|
||||
!disabled?.() && "group",
|
||||
classes,
|
||||
"flex-none rounded-lg p-2 disabled:opacity-50",
|
||||
])}
|
||||
onClick={onClick}
|
||||
>
|
||||
<Dynamic
|
||||
component={icon()}
|
||||
class="size-[1.125rem] group-active:scale-90"
|
||||
/>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import { classPropToString } from "/src/solid/classes";
|
||||
|
||||
export function Button({
|
||||
title,
|
||||
icon,
|
||||
colors,
|
||||
onClick,
|
||||
disabled,
|
||||
classes,
|
||||
}: {
|
||||
title: string;
|
||||
icon: () => ValidComponent;
|
||||
colors?: () => string;
|
||||
onClick: VoidFunction;
|
||||
disabled?: () => boolean;
|
||||
classes?: string;
|
||||
}) {
|
||||
return (
|
||||
<button
|
||||
title={title}
|
||||
disabled={disabled?.()}
|
||||
class={classPropToString([
|
||||
colors?.() || (disabled?.() ? "" : "hover:bg-orange-200/15"),
|
||||
!disabled?.() && "group",
|
||||
classes,
|
||||
"flex-none rounded-lg p-2 disabled:opacity-50",
|
||||
])}
|
||||
onClick={onClick}
|
||||
>
|
||||
<Dynamic
|
||||
component={icon()}
|
||||
class="size-[1.125rem] group-active:scale-90"
|
||||
/>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import { generate } from "lean-qr";
|
||||
|
||||
import { Button } from "./button";
|
||||
|
||||
export function ButtonShare({ qrcode }: { qrcode: RWS<string> }) {
|
||||
return (
|
||||
<Button
|
||||
title="Share"
|
||||
icon={() => IconTablerShare}
|
||||
onClick={() => {
|
||||
qrcode.set(() =>
|
||||
generate(document.location.href).toDataURL({
|
||||
on: [0xff, 0xff, 0xff, 0xff],
|
||||
off: [0x00, 0x00, 0x00, 0x00],
|
||||
padX: 0,
|
||||
padY: 0,
|
||||
}),
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -1,33 +1,81 @@
|
||||
import { cleanChart } from "/src/scripts/lightweightCharts/chart/clean";
|
||||
import { renderChart } from "/src/scripts/lightweightCharts/chart/render";
|
||||
import { requestIdleCallbackPossible } from "/src/env";
|
||||
import { createRWS } from "/src/solid/rws";
|
||||
|
||||
export function Chart({
|
||||
charts,
|
||||
parentDiv,
|
||||
presets,
|
||||
datasets,
|
||||
legendSetter,
|
||||
activeResources,
|
||||
dark,
|
||||
activeIds,
|
||||
}: {
|
||||
charts: RWS<IChartApi[]>;
|
||||
parentDiv: RWS<HTMLDivElement | undefined>;
|
||||
presets: Presets;
|
||||
datasets: Datasets;
|
||||
legendSetter: Setter<PresetLegend>;
|
||||
activeResources: Accessor<Set<ResourceDataset<any, any>>>;
|
||||
legendSetter: Setter<SeriesLegend[]>;
|
||||
dark: Accessor<boolean>;
|
||||
activeIds: RWS<number[]>;
|
||||
}) {
|
||||
const wasIdle = createRWS(false);
|
||||
|
||||
if (requestIdleCallbackPossible) {
|
||||
const idleCallback = requestIdleCallback(() => {
|
||||
console.log("idle");
|
||||
wasIdle.set(true);
|
||||
cancelIdleCallback(idleCallback);
|
||||
});
|
||||
|
||||
onCleanup(() => {
|
||||
cancelIdleCallback(idleCallback);
|
||||
});
|
||||
} else {
|
||||
const timeout = setTimeout(() => {
|
||||
console.log("timeout");
|
||||
wasIdle.set(true);
|
||||
}, 500);
|
||||
|
||||
onCleanup(() => {
|
||||
clearTimeout(timeout);
|
||||
});
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
createEffect(() => {
|
||||
const preset = presets.selected();
|
||||
const div = parentDiv();
|
||||
|
||||
untrack(() =>
|
||||
renderChart({
|
||||
datasets,
|
||||
preset,
|
||||
legendSetter,
|
||||
activeResources,
|
||||
}),
|
||||
);
|
||||
if (!wasIdle() || !div) return;
|
||||
|
||||
untrack(() => {
|
||||
try {
|
||||
console.log(`preset: ${preset.id}`);
|
||||
preset.applyPreset({
|
||||
charts,
|
||||
parentDiv: div,
|
||||
datasets,
|
||||
preset,
|
||||
legendSetter,
|
||||
dark,
|
||||
activeIds,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("chart: render: failed", error);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
onCleanup(cleanChart);
|
||||
onCleanup(() =>
|
||||
charts.set((charts) => {
|
||||
charts.forEach((chart) => {
|
||||
chart.remove();
|
||||
});
|
||||
|
||||
return [];
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
return <div id="chart" class="h-full w-full cursor-crosshair" />;
|
||||
return <></>;
|
||||
}
|
||||
|
||||
@@ -1,56 +1,97 @@
|
||||
import { chunkIdToIndex } from "/src/scripts/datasets/resource";
|
||||
import { createRWS } from "/src/solid/rws";
|
||||
|
||||
const transparency = "66";
|
||||
import { Scrollable } from "../../scrollable";
|
||||
|
||||
const transparency = "44";
|
||||
|
||||
export function Legend({
|
||||
scale,
|
||||
legend: legendList,
|
||||
dark,
|
||||
activeIds,
|
||||
}: {
|
||||
legend: Accessor<PresetLegend>;
|
||||
scale: Accessor<ResourceScale>;
|
||||
legend: Accessor<SeriesLegend[]>;
|
||||
dark: Accessor<boolean>;
|
||||
activeIds: Accessor<number[]>;
|
||||
}) {
|
||||
const hovering = createRWS<SeriesLegend | undefined>(undefined);
|
||||
const hovered = createRWS<SeriesLegend | undefined>(undefined);
|
||||
|
||||
let toggle = false;
|
||||
|
||||
return (
|
||||
<div class="flex flex-1 items-center gap-1 overflow-y-auto">
|
||||
<Scrollable classes="items-center gap-1 p-1.5">
|
||||
<For each={legendList()}>
|
||||
{(legend) => {
|
||||
const initialColors = {} as any;
|
||||
const darkenColors = {} as any;
|
||||
|
||||
Object.entries(legend.series.options()).forEach(([k, v]) => {
|
||||
if (k.toLowerCase().includes("color") && v) {
|
||||
initialColors[k] = v;
|
||||
darkenColors[k] = `${v}${transparency}`;
|
||||
} else if (k === "lastValueVisible" && v) {
|
||||
initialColors[k] = v;
|
||||
darkenColors[k] = !v;
|
||||
}
|
||||
});
|
||||
|
||||
createEffect(() => {
|
||||
if (hovering()) {
|
||||
if (hovering()?.title !== legend.title) {
|
||||
legend.series.applyOptions(darkenColors);
|
||||
}
|
||||
} else {
|
||||
legend.series.applyOptions(initialColors);
|
||||
const range = activeIds();
|
||||
|
||||
for (let i = 0; i < range.length; i++) {
|
||||
const id = range[i];
|
||||
|
||||
const initialColors = {} as any;
|
||||
const darkenColors = {} as any;
|
||||
|
||||
const chunkIndex = chunkIdToIndex(scale(), id);
|
||||
|
||||
const series = legend.seriesList.at(chunkIndex)?.();
|
||||
|
||||
if (!series) return;
|
||||
|
||||
const seriesOptions = series.options();
|
||||
|
||||
if (!seriesOptions) continue;
|
||||
|
||||
Object.entries(seriesOptions).forEach(([k, v]) => {
|
||||
if (k.toLowerCase().includes("color") && v) {
|
||||
if (typeof v === "string" && !v.startsWith("#")) {
|
||||
return;
|
||||
}
|
||||
|
||||
v = (v as string).substring(0, 7);
|
||||
initialColors[k] = v;
|
||||
darkenColors[k] = `${v}${transparency}`;
|
||||
} else if (k === "lastValueVisible" && v) {
|
||||
initialColors[k] = true;
|
||||
darkenColors[k] = false;
|
||||
}
|
||||
});
|
||||
|
||||
createEffect((wasHovering: boolean) => {
|
||||
const hoveredLegend = hovered();
|
||||
const hovering = !!hovered();
|
||||
|
||||
if (wasHovering === hovering) {
|
||||
return hovering;
|
||||
}
|
||||
|
||||
if (hoveredLegend) {
|
||||
if (hoveredLegend.title !== legend.title) {
|
||||
series.applyOptions(darkenColors);
|
||||
}
|
||||
} else {
|
||||
series.applyOptions(initialColors);
|
||||
}
|
||||
|
||||
return hovering;
|
||||
}, false);
|
||||
}
|
||||
});
|
||||
|
||||
let previousClickValueOf: number = 0;
|
||||
let previousClickTime: number = 0;
|
||||
|
||||
return (
|
||||
<Show when={!legend.disabled()}>
|
||||
<button
|
||||
onMouseEnter={() => {
|
||||
hovering.set(legend);
|
||||
}}
|
||||
onMouseLeave={() => hovering.set(undefined)}
|
||||
onMouseEnter={() => legend.visible() && hovered.set(legend)}
|
||||
onMouseLeave={() => hovered.set(undefined)}
|
||||
onTouchStart={() => legend.visible() && hovered.set(legend)}
|
||||
onTouchEnd={() => hovered.set(undefined)}
|
||||
onClick={() => {
|
||||
const currentClickValueOf = new Date().valueOf();
|
||||
const currentClickTime = new Date().getTime();
|
||||
|
||||
if (currentClickValueOf - previousClickValueOf > 300) {
|
||||
if (currentClickTime - previousClickTime > 300) {
|
||||
legend.visible.set((visible) => !visible);
|
||||
} else {
|
||||
legendList().forEach((_legend) => {
|
||||
@@ -64,9 +105,15 @@ export function Legend({
|
||||
toggle = !toggle;
|
||||
}
|
||||
|
||||
previousClickValueOf = currentClickValueOf;
|
||||
previousClickTime = currentClickTime;
|
||||
|
||||
if (legend.visible()) {
|
||||
hovered.set(legend);
|
||||
} else {
|
||||
hovered.set(undefined);
|
||||
}
|
||||
}}
|
||||
class="flex flex-none items-center space-x-1.5 rounded-full py-1.5 pl-2 pr-2.5 hover:bg-orange-200/20 active:scale-[0.975]"
|
||||
class="flex flex-none items-center space-x-1.5 rounded-full py-1.5 pl-2 pr-2.5 hover:bg-orange-800/20 active:scale-[0.975] dark:hover:bg-orange-200/20"
|
||||
>
|
||||
<span
|
||||
class="flex size-4 flex-col overflow-hidden rounded-full"
|
||||
@@ -76,9 +123,9 @@ export function Legend({
|
||||
>
|
||||
<For
|
||||
each={
|
||||
Array.isArray(legend.color())
|
||||
? (legend.color() as string[])
|
||||
: [legend.color() as string]
|
||||
Array.isArray(legend.color)
|
||||
? legend.color.map((c) => c(dark))
|
||||
: [legend.color(dark)]
|
||||
}
|
||||
>
|
||||
{(color) => (
|
||||
@@ -92,7 +139,7 @@ export function Legend({
|
||||
</For>
|
||||
</span>
|
||||
<span
|
||||
class="text-white decoration-white decoration-wavy decoration-[1.5px]"
|
||||
class="text-high-contrast decoration-high-contrast decoration-wavy decoration-[1.5px]"
|
||||
style={{
|
||||
"text-decoration-line": !legend.visible()
|
||||
? "line-through"
|
||||
@@ -102,17 +149,13 @@ export function Legend({
|
||||
>
|
||||
{legend.title}
|
||||
</span>
|
||||
<Show when={legend.url}>
|
||||
<Show when={legend.dataset.url}>
|
||||
{(url) => (
|
||||
<a
|
||||
title="Dataset"
|
||||
class="-my-0.5 !-mr-1 inline-flex size-6 flex-col overflow-hidden rounded-full border border-orange-200/5 bg-orange-200 bg-opacity-5 p-1 pl-0.5 hover:bg-opacity-30"
|
||||
style={{
|
||||
opacity: legend.visible() ? 1 : 0.5,
|
||||
}}
|
||||
class="border-superlight -my-0.5 !-mr-1 inline-flex size-6 flex-col overflow-hidden rounded-full border bg-white bg-opacity-5 p-1 pl-0.5 hover:bg-opacity-50 dark:bg-orange-200 dark:bg-opacity-5 dark:hover:bg-opacity-25"
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
// event.preventDefault();
|
||||
}}
|
||||
href={url()}
|
||||
target={
|
||||
@@ -130,6 +173,6 @@ export function Legend({
|
||||
);
|
||||
}}
|
||||
</For>
|
||||
</div>
|
||||
</Scrollable>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,67 +1,225 @@
|
||||
import { chartState } from "/src/scripts/lightweightCharts/chart/state";
|
||||
import { GENESIS_DAY } from "/src/scripts/lightweightCharts/chart/whitespace";
|
||||
import { ONE_DAY_IN_MS } from "/src/scripts/utils/time";
|
||||
import { classPropToString } from "/src/solid/classes";
|
||||
|
||||
import { GENESIS_DAY } from "../../../../../scripts/lightweightCharts/whitespace";
|
||||
import { Box } from "../../box";
|
||||
import { Scrollable } from "../../scrollable";
|
||||
|
||||
export function TimeScale({
|
||||
scale,
|
||||
charts,
|
||||
}: {
|
||||
scale: Accessor<ResourceScale>;
|
||||
charts: RWS<IChartApi[]>;
|
||||
}) {
|
||||
const today = new Date();
|
||||
|
||||
const disabled = createMemo(() => charts().length === 0);
|
||||
|
||||
export function TimeScale() {
|
||||
return (
|
||||
<Box dark padded overflowY>
|
||||
<Button onClick={() => setTimeScale()}>All Time</Button>
|
||||
<Button onClick={() => setTimeScale(7)}>1 Week</Button>
|
||||
<Button onClick={() => setTimeScale(30)}>1 Month</Button>
|
||||
<Button onClick={() => setTimeScale(30 * 6)}>6 Months</Button>
|
||||
<Button
|
||||
onClick={() =>
|
||||
setTimeScale(
|
||||
Math.ceil(
|
||||
(new Date().valueOf() -
|
||||
new Date(`${new Date().getUTCFullYear()}-01-01`).valueOf()) /
|
||||
ONE_DAY_IN_MS,
|
||||
),
|
||||
)
|
||||
}
|
||||
>
|
||||
Year To Date
|
||||
</Button>
|
||||
<Button onClick={() => setTimeScale(365)}>1 Year</Button>
|
||||
<Button onClick={() => setTimeScale(2 * 365)}>2 Years</Button>
|
||||
<Button onClick={() => setTimeScale(4 * 365)}>4 Years</Button>
|
||||
<Button onClick={() => setTimeScale(8 * 365)}>8 Years</Button>
|
||||
<Box dark padded={false} classes="short:hidden">
|
||||
<Scrollable classes="p-1.5 space-x-2">
|
||||
<Switch>
|
||||
<Match when={scale() === "date"}>
|
||||
<Button
|
||||
disabled={disabled}
|
||||
onClick={() => setTimeScale({ scale: scale(), charts })}
|
||||
>
|
||||
All Time
|
||||
</Button>
|
||||
<Button
|
||||
disabled={disabled}
|
||||
onClick={() => setTimeScale({ scale: scale(), charts, days: 7 })}
|
||||
>
|
||||
1 Week
|
||||
</Button>
|
||||
<Button
|
||||
disabled={disabled}
|
||||
onClick={() => setTimeScale({ scale: scale(), charts, days: 30 })}
|
||||
>
|
||||
1 Month
|
||||
</Button>
|
||||
<Button
|
||||
disabled={disabled}
|
||||
onClick={() =>
|
||||
setTimeScale({ scale: scale(), charts, days: 3 * 30 })
|
||||
}
|
||||
>
|
||||
3 Months
|
||||
</Button>
|
||||
<Button
|
||||
disabled={disabled}
|
||||
onClick={() =>
|
||||
setTimeScale({ scale: scale(), charts, days: 6 * 30 })
|
||||
}
|
||||
>
|
||||
6 Months
|
||||
</Button>
|
||||
<Button
|
||||
disabled={disabled}
|
||||
onClick={() =>
|
||||
setTimeScale({
|
||||
scale: scale(),
|
||||
charts,
|
||||
days: Math.ceil(
|
||||
(today.getTime() -
|
||||
new Date(`${today.getUTCFullYear()}-01-01`).getTime()) /
|
||||
ONE_DAY_IN_MS,
|
||||
),
|
||||
})
|
||||
}
|
||||
>
|
||||
Year To Date
|
||||
</Button>
|
||||
<Button
|
||||
disabled={disabled}
|
||||
onClick={() =>
|
||||
setTimeScale({ scale: scale(), charts, days: 365 })
|
||||
}
|
||||
>
|
||||
1 Year
|
||||
</Button>
|
||||
<Button
|
||||
disabled={disabled}
|
||||
onClick={() =>
|
||||
setTimeScale({ scale: scale(), charts, days: 2 * 365 })
|
||||
}
|
||||
>
|
||||
2 Years
|
||||
</Button>
|
||||
<Button
|
||||
disabled={disabled}
|
||||
onClick={() =>
|
||||
setTimeScale({ scale: scale(), charts, days: 4 * 365 })
|
||||
}
|
||||
>
|
||||
4 Years
|
||||
</Button>
|
||||
<Button
|
||||
disabled={disabled}
|
||||
onClick={() =>
|
||||
setTimeScale({ scale: scale(), charts, days: 8 * 365 })
|
||||
}
|
||||
>
|
||||
8 Years
|
||||
</Button>
|
||||
<For
|
||||
each={new Array(
|
||||
new Date().getFullYear() - new Date("2009-01-01").getFullYear(),
|
||||
)
|
||||
.fill(0)
|
||||
.map((_, index) => index + 2009)
|
||||
.reverse()}
|
||||
>
|
||||
{(year) => (
|
||||
<Button
|
||||
disabled={disabled}
|
||||
onClick={() => setTimeScale({ scale: scale(), charts, year })}
|
||||
>
|
||||
{year}
|
||||
</Button>
|
||||
)}
|
||||
</For>
|
||||
</Match>
|
||||
<Match when={scale() === "height"}>
|
||||
<Button disabled={() => true} onClick={() => {}}>
|
||||
24h
|
||||
</Button>
|
||||
<Button disabled={() => true} onClick={() => {}}>
|
||||
48h
|
||||
</Button>
|
||||
<For
|
||||
each={new Array(9)
|
||||
.fill(0)
|
||||
.flatMap((_, i) => [i, i + 0.5])
|
||||
.reverse()}
|
||||
>
|
||||
{(i) => (
|
||||
<Button
|
||||
disabled={disabled}
|
||||
onClick={() =>
|
||||
setTimeScale({
|
||||
scale: scale(),
|
||||
charts,
|
||||
range: {
|
||||
from: i * 100_000,
|
||||
to: (i + 0.5) * 100_000,
|
||||
},
|
||||
})
|
||||
}
|
||||
>
|
||||
{`${100 * (i + 0.5)}k`}
|
||||
</Button>
|
||||
)}
|
||||
</For>
|
||||
</Match>
|
||||
</Switch>
|
||||
</Scrollable>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
function Button(props: ParentProps & { onClick: VoidFunction }) {
|
||||
function Button({
|
||||
onClick,
|
||||
disabled,
|
||||
children,
|
||||
}: ParentProps & { onClick: VoidFunction; disabled: Accessor<boolean> }) {
|
||||
return (
|
||||
<button
|
||||
class="min-w-20 flex-shrink-0 flex-grow whitespace-nowrap rounded-lg px-2 py-1.5 hover:bg-white/20 active:scale-95"
|
||||
onClick={props.onClick}
|
||||
class={classPropToString([
|
||||
disabled() ? "opacity-50" : "hover:bg-orange-50/20 active:scale-95",
|
||||
"min-w-20 flex-shrink-0 flex-grow whitespace-nowrap rounded-lg px-2 py-1.5",
|
||||
])}
|
||||
onClick={onClick}
|
||||
disabled={disabled()}
|
||||
>
|
||||
{props.children}
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
function setTimeScale(days?: number) {
|
||||
const to = new Date();
|
||||
function setTimeScale({
|
||||
charts,
|
||||
scale,
|
||||
days,
|
||||
year,
|
||||
range,
|
||||
}: {
|
||||
charts: RWS<IChartApi[]>;
|
||||
scale: ResourceScale;
|
||||
days?: number;
|
||||
year?: number;
|
||||
range?: { from: number; to: number };
|
||||
}) {
|
||||
if (scale === "date") {
|
||||
let from = new Date();
|
||||
let to = new Date();
|
||||
|
||||
if (days) {
|
||||
const from = new Date();
|
||||
from.setDate(from.getUTCDate() - days);
|
||||
if (year) {
|
||||
from = new Date(`${year}-01-01`);
|
||||
to = new Date(`${year}-12-31`);
|
||||
} else if (days) {
|
||||
from.setDate(from.getUTCDate() - days);
|
||||
} else {
|
||||
from = new Date(GENESIS_DAY);
|
||||
}
|
||||
|
||||
chartState.chart?.timeScale().setVisibleRange({
|
||||
from: (from.getTime() / 1000) as Time,
|
||||
to: (to.getTime() / 1000) as Time,
|
||||
});
|
||||
} else {
|
||||
// chartState.chart?.timeScale().fitContent();
|
||||
chartState.chart?.timeScale().setVisibleRange({
|
||||
from: (new Date(
|
||||
// datasets.candlesticks.values()?.[0]?.date || "",
|
||||
GENESIS_DAY,
|
||||
).getTime() / 1000) as Time,
|
||||
to: (to.getTime() / 1000) as Time,
|
||||
});
|
||||
charts()
|
||||
.at(0)
|
||||
?.timeScale()
|
||||
.setVisibleRange({
|
||||
from: (from.getTime() / 1000) as Time,
|
||||
to: (to.getTime() / 1000) as Time,
|
||||
});
|
||||
} else if (scale === "height") {
|
||||
if (range) {
|
||||
charts()
|
||||
.at(0)
|
||||
?.timeScale()
|
||||
.setVisibleRange({
|
||||
from: range.from as Time,
|
||||
to: range.to as Time,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
export function Title({ presets }: { presets: Presets }) {
|
||||
return (
|
||||
<div class="flex flex-1 items-center overflow-y-auto pb-1.5 text-orange-100/50">
|
||||
<div class="flex flex-1 items-center overflow-y-auto p-1.5">
|
||||
<div class="flex-1 -space-y-1 whitespace-nowrap px-1 md:mt-0.5 md:-space-y-1.5">
|
||||
<h3 class="text-xs">{`/ ${[...presets.selected().path.map(({ name }) => name), presets.selected().name].join(" / ")}`}</h3>
|
||||
<h1 class="text-lg font-bold text-white md:text-xl">
|
||||
{presets.selected().title}
|
||||
</h1>
|
||||
<h3 class="text-xs opacity-50">{`/ ${[...presets.selected().path.map(({ name }) => name), presets.selected().name].join(" / ")}`}</h3>
|
||||
<h1 class="text-lg font-bold md:text-xl">{presets.selected().title}</h1>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -3,7 +3,6 @@ import { createRWS } from "/src/solid/rws";
|
||||
|
||||
import { Box } from "../box";
|
||||
import { Actions } from "./components/actions";
|
||||
import { Chart } from "./components/chart";
|
||||
import { Legend } from "./components/legend";
|
||||
import { TimeScale } from "./components/timeScale";
|
||||
import { Title } from "./components/title";
|
||||
@@ -11,58 +10,77 @@ import { Title } from "./components/title";
|
||||
export function ChartFrame({
|
||||
presets,
|
||||
datasets,
|
||||
activeResources,
|
||||
hide,
|
||||
qrcode,
|
||||
standalone,
|
||||
fullscreen,
|
||||
dark,
|
||||
}: {
|
||||
presets: Presets;
|
||||
hide?: Accessor<boolean>;
|
||||
qrcode: RWS<string>;
|
||||
activeResources: Accessor<Set<ResourceDataset<any, any>>>;
|
||||
datasets: Datasets;
|
||||
fullscreen?: RWS<boolean>;
|
||||
dark: Accessor<boolean>;
|
||||
standalone: boolean;
|
||||
}) {
|
||||
const legend = createRWS<PresetLegend>([]);
|
||||
const legend = createRWS<SeriesLegend[]>([]);
|
||||
|
||||
const charts = createRWS<IChartApi[]>([]);
|
||||
|
||||
const div = createRWS<HTMLDivElement | undefined>(undefined);
|
||||
|
||||
const scale = createMemo(() => presets.selected().scale);
|
||||
|
||||
const activeIds = createRWS([] as number[], { equals: false });
|
||||
|
||||
const Chart = lazy(() =>
|
||||
import("./components/chart").then((d) => ({ default: d.Chart })),
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
class={classPropToString([
|
||||
standalone &&
|
||||
"rounded-2xl border border-orange-200/15 bg-gradient-to-b from-orange-100/5 to-black/10 to-80%",
|
||||
"border-lighter rounded-2xl border bg-gradient-to-b from-white/15 to-white/30 to-80% shadow-md dark:from-orange-100/5 dark:to-black/10",
|
||||
"flex size-full min-h-0 flex-1 flex-col overflow-hidden",
|
||||
])}
|
||||
style={{
|
||||
display: (hide ? !hide() : true) ? undefined : "none",
|
||||
display: (hide ? hide() : false) ? "none" : undefined,
|
||||
}}
|
||||
>
|
||||
<Box flex={false} dark>
|
||||
<Box flex={false} dark padded={false} classes="short:hidden">
|
||||
<Title presets={presets} />
|
||||
|
||||
<div class="-mx-2 border-t border-orange-200/15" />
|
||||
<div class="border-lighter border-t" />
|
||||
|
||||
<div class="flex pt-1.5">
|
||||
<Legend legend={legend} />
|
||||
<div class="flex">
|
||||
<Legend
|
||||
legend={legend}
|
||||
scale={scale}
|
||||
activeIds={activeIds}
|
||||
dark={dark}
|
||||
/>
|
||||
|
||||
<div class="-my-1.5 border-l border-orange-200/15 pr-1.5" />
|
||||
<div class="border-lighter border-l" />
|
||||
|
||||
<Actions presets={presets} qrcode={qrcode} fullscreen={fullscreen} />
|
||||
</div>
|
||||
</Box>
|
||||
|
||||
<div class="-mt-2 min-h-0 flex-1">
|
||||
<div ref={div.set} class="-mr-2 -mt-2 flex min-h-0 flex-1 flex-col">
|
||||
<Chart
|
||||
activeResources={activeResources}
|
||||
parentDiv={div}
|
||||
charts={charts}
|
||||
datasets={datasets}
|
||||
// fetchedDatasets={fetchedDatasets}
|
||||
legendSetter={legend.set}
|
||||
presets={presets}
|
||||
dark={dark}
|
||||
activeIds={activeIds}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<TimeScale />
|
||||
<TimeScale charts={charts} scale={scale} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -12,7 +12,9 @@ export function FavoritesFrame({
|
||||
return (
|
||||
<div
|
||||
class="flex-1 overflow-y-auto"
|
||||
hidden={selectedFrame() !== "Favorites"}
|
||||
style={{
|
||||
display: selectedFrame() !== "Favorites" ? "none" : undefined,
|
||||
}}
|
||||
>
|
||||
<div class="flex max-h-full min-h-0 flex-1 flex-col gap-4 p-4">
|
||||
<Header title="Favorites">
|
||||
@@ -20,7 +22,7 @@ export function FavoritesFrame({
|
||||
favorites.
|
||||
</Header>
|
||||
|
||||
<div class="-mx-4 border-t border-orange-200/10" />
|
||||
<div class="border-lighter -mx-4 border-t" />
|
||||
|
||||
<div
|
||||
class="space-y-0.5 py-1"
|
||||
|
||||
+2
-2
@@ -26,10 +26,10 @@ export function Folder({
|
||||
name={name}
|
||||
icon={icon}
|
||||
onClick={onClick}
|
||||
classes={() => (open() ? "text-orange-100/75" : "")}
|
||||
classes={() => (open() ? "opacity-60" : "")}
|
||||
tail={() => (
|
||||
<Show when={!open()}>
|
||||
<span class="rounded-full bg-white bg-opacity-[0.075] px-2 py-0.5 text-xs text-neutral-400">
|
||||
<span class="rounded-full bg-orange-50/10 px-2 py-0.5 text-xs text-neutral-400">
|
||||
{children}
|
||||
</span>
|
||||
</Show>
|
||||
@@ -0,0 +1,119 @@
|
||||
import { File } from "./file";
|
||||
import { Folder } from "./folder";
|
||||
|
||||
export function Tree({
|
||||
tree,
|
||||
selected,
|
||||
openedFolders,
|
||||
depth = 0,
|
||||
visible,
|
||||
selectPreset,
|
||||
path = [],
|
||||
favorites,
|
||||
}: {
|
||||
tree: PresetTree;
|
||||
selected: Accessor<Preset>;
|
||||
selectPreset(preset: Preset): void;
|
||||
openedFolders: RWS<Set<string>>;
|
||||
depth?: number;
|
||||
visible?: Accessor<boolean>;
|
||||
path?: FilePath;
|
||||
favorites: Accessor<Preset[]>;
|
||||
}) {
|
||||
return (
|
||||
<Show when={visible?.() || !visible}>
|
||||
<div>
|
||||
<For each={tree}>
|
||||
{(thing) => {
|
||||
const active = createMemo(() => thing.id === selected().id);
|
||||
const favorite = createMemo(() =>
|
||||
favorites().includes(thing as Preset),
|
||||
);
|
||||
const visited = (thing as Preset).visited;
|
||||
|
||||
if (!("tree" in thing)) {
|
||||
return (
|
||||
<File
|
||||
id={thing.id}
|
||||
name={thing.name}
|
||||
active={active}
|
||||
depth={depth}
|
||||
icon={thing.icon || IconTablerFile}
|
||||
favorite={favorite}
|
||||
visited={visited}
|
||||
onClick={() => {
|
||||
const selectedId = selected().id;
|
||||
|
||||
if (selectedId === thing.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Has been filled in createPresets
|
||||
selectPreset(thing as Preset);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const childrenVisible = createMemo(() =>
|
||||
openedFolders().has(thing.id),
|
||||
);
|
||||
|
||||
const childCount = countChildren(thing);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Folder
|
||||
id={thing.id}
|
||||
name={thing.name}
|
||||
depth={depth}
|
||||
open={childrenVisible}
|
||||
children={childCount}
|
||||
onClick={() => {
|
||||
openedFolders.set((s) => {
|
||||
if (childrenVisible()) {
|
||||
s.delete(thing.id);
|
||||
} else {
|
||||
s.add(thing.id);
|
||||
}
|
||||
|
||||
return s;
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<Tree
|
||||
tree={thing.tree}
|
||||
selected={selected}
|
||||
depth={depth + 1}
|
||||
openedFolders={openedFolders}
|
||||
visible={childrenVisible}
|
||||
path={[...path, { name: thing.name, id: thing.id }]}
|
||||
selectPreset={selectPreset}
|
||||
favorites={favorites}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
</For>
|
||||
</div>
|
||||
</Show>
|
||||
);
|
||||
}
|
||||
|
||||
function countChildren(folder: PresetFolder) {
|
||||
let count = 0;
|
||||
|
||||
function _countChildren(tree: PartialPresetTree) {
|
||||
tree.forEach((anyPreset) => {
|
||||
if ("tree" in anyPreset) {
|
||||
_countChildren(anyPreset.tree);
|
||||
} else {
|
||||
count += 1;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
_countChildren(folder.tree);
|
||||
|
||||
return count;
|
||||
}
|
||||
+6
-5
@@ -1,5 +1,6 @@
|
||||
import { scrollIntoView } from "/src/scripts/utils/scroll";
|
||||
import { sleep, tick } from "/src/scripts/utils/sleep";
|
||||
import { sleep } from "/src/scripts/utils/sleep";
|
||||
import { tick } from "/src/scripts/utils/tick";
|
||||
import { createRWS } from "/src/solid/rws";
|
||||
|
||||
import { Box } from "../box";
|
||||
@@ -8,7 +9,7 @@ import { Header } from "../header";
|
||||
import { Number } from "../number";
|
||||
import { Tree } from "./components/tree";
|
||||
|
||||
export function TreeFrame({
|
||||
export function FoldersFrame({
|
||||
presets,
|
||||
selectedFrame,
|
||||
}: {
|
||||
@@ -25,17 +26,17 @@ export function TreeFrame({
|
||||
<div
|
||||
class="relative flex size-full flex-1 flex-col"
|
||||
style={{
|
||||
display: selectedFrame() !== "Tree" ? "none" : undefined,
|
||||
display: selectedFrame() !== "Folders" ? "none" : undefined,
|
||||
}}
|
||||
>
|
||||
<div class="flex-1 overflow-y-auto">
|
||||
<div class="flex max-h-full min-h-0 flex-1 flex-col gap-4 p-4">
|
||||
<Header title="Folders">
|
||||
<Number number={() => presets.list.length} /> presets organized in a
|
||||
<Number number={() => presets.list.length} /> charts organized in a
|
||||
tree like structure.
|
||||
</Header>
|
||||
|
||||
<div class="-mx-4 border-t border-orange-200/10" />
|
||||
<div class="border-lighter -mx-4 border-t" />
|
||||
|
||||
<Tree
|
||||
tree={presets.tree}
|
||||
@@ -2,7 +2,7 @@ export function Header({ title, children }: { title: string } & ParentProps) {
|
||||
return (
|
||||
<div>
|
||||
<h3 class="text-lg font-bold md:text-xl">{title}</h3>
|
||||
<p class="text-orange-100/75">{children}</p>
|
||||
<p class="text-orange-950/60 dark:text-orange-100/75">{children}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -11,9 +11,14 @@ export function HistoryFrame({
|
||||
selectedFrame: Accessor<FrameName>;
|
||||
}) {
|
||||
return (
|
||||
<div class="flex-1 overflow-y-auto" hidden={selectedFrame() !== "History"}>
|
||||
<div
|
||||
class="flex-1 overflow-y-auto"
|
||||
style={{
|
||||
display: selectedFrame() !== "History" ? "none" : undefined,
|
||||
}}
|
||||
>
|
||||
<div class="flex max-h-full min-h-0 flex-1 flex-col p-4">
|
||||
<Header title="History">List of previously visited presets.</Header>
|
||||
<Header title="History">List of previously visited charts.</Header>
|
||||
|
||||
<div
|
||||
class="space-y-0.5 pt-4"
|
||||
@@ -31,8 +36,8 @@ export function HistoryFrame({
|
||||
presets.history()[index() - 1].date.toJSON().split("T")[0]
|
||||
}
|
||||
>
|
||||
<div class="sticky top-[-0.5rem] z-10 -mx-4 py-2">
|
||||
<div class="border-y border-orange-200/10 bg-[rgb(25,15,15)] p-2">
|
||||
<div class="sticky top-[calc(-0.5rem-1px)] z-10 -mx-4 py-2">
|
||||
<div class="border-lighter border-y bg-[#F4EAE3] p-2 dark:bg-[rgb(25,15,15)]">
|
||||
<p class="ml-2">
|
||||
<Switch fallback={date.toLocaleDateString()}>
|
||||
<Match
|
||||
|
||||
@@ -45,9 +45,7 @@ export function Line({
|
||||
title={name}
|
||||
>
|
||||
<For each={new Array(depth)}>
|
||||
{() => (
|
||||
<span class="ml-1 h-8 w-3 flex-none border-l border-orange-200/10" />
|
||||
)}
|
||||
{() => <span class="border-lighter ml-1 h-8 w-3 flex-none border-l" />}
|
||||
</For>
|
||||
<Show when={icon}>
|
||||
{(icon) => (
|
||||
@@ -68,10 +66,7 @@ export function Line({
|
||||
])}
|
||||
>
|
||||
<Show when={header}>
|
||||
<span
|
||||
class="truncate text-xs text-white text-opacity-50"
|
||||
innerHTML={header}
|
||||
/>
|
||||
<span class="truncate text-xs opacity-50" innerHTML={header} />
|
||||
</Show>
|
||||
<span class="space-x-1 truncate">
|
||||
<span innerHTML={name} />
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
import { createResizeObserver } from "@solid-primitives/resize-observer";
|
||||
|
||||
import { touchScreen } from "/src/env";
|
||||
import { classPropToString } from "/src/solid/classes";
|
||||
import { createRWS } from "/src/solid/rws";
|
||||
|
||||
export function Scrollable({
|
||||
children,
|
||||
classes,
|
||||
}: {
|
||||
classes?: string;
|
||||
} & ParentProps) {
|
||||
const maybeScrollable = createRWS<HTMLDivElement | undefined>(undefined);
|
||||
const scrollable = createRWS(false);
|
||||
const showLeftArrow = createRWS(false);
|
||||
const showRightArrow = createRWS(false);
|
||||
|
||||
onMount(() => {
|
||||
createResizeObserver(maybeScrollable, (_, el) => {
|
||||
if (el !== maybeScrollable()) {
|
||||
return;
|
||||
}
|
||||
|
||||
checkScrollable();
|
||||
});
|
||||
});
|
||||
|
||||
function checkScrollable() {
|
||||
const div = maybeScrollable();
|
||||
|
||||
if (div) {
|
||||
scrollable.set(() => div.scrollWidth > div.clientWidth);
|
||||
}
|
||||
|
||||
checkArrows();
|
||||
}
|
||||
|
||||
function checkArrows() {
|
||||
const target = maybeScrollable()!;
|
||||
|
||||
const left = target.scrollLeft;
|
||||
const right =
|
||||
target.scrollWidth - Math.ceil(target.scrollLeft + target.clientWidth);
|
||||
|
||||
showLeftArrow.set(() => left > 0);
|
||||
showRightArrow.set(() => right > 0);
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
createEffect(on(children, checkScrollable));
|
||||
|
||||
return (
|
||||
<div class="relative min-w-0 flex-1">
|
||||
<For
|
||||
each={[
|
||||
{
|
||||
showArrow: showLeftArrow,
|
||||
side: "left-0",
|
||||
order: "",
|
||||
buttonPadding: "pl-2",
|
||||
iconPadding: "pr-0.5",
|
||||
scrollMultiplier: -1,
|
||||
chevronIcon: IconTablerChevronLeft,
|
||||
gradientDirection: "bg-gradient-to-r",
|
||||
},
|
||||
{
|
||||
showArrow: showRightArrow,
|
||||
side: "right-0",
|
||||
order: "order-2",
|
||||
buttonPadding: "pr-2",
|
||||
iconPadding: "pl-0.5",
|
||||
scrollMultiplier: 1,
|
||||
chevronIcon: IconTablerChevronRight,
|
||||
gradientDirection: "bg-gradient-to-l",
|
||||
},
|
||||
]}
|
||||
>
|
||||
{(obj) => (
|
||||
<Show when={scrollable() && obj.showArrow()}>
|
||||
<div
|
||||
class={[
|
||||
obj.side,
|
||||
"pointer-events-none absolute bottom-0 top-0 flex transition-opacity duration-200 ease-in-out",
|
||||
].join(" ")}
|
||||
>
|
||||
<Show when={!touchScreen}>
|
||||
<div
|
||||
class={[
|
||||
obj.order,
|
||||
obj.buttonPadding,
|
||||
"pointer-events-auto flex h-full items-center bg-stone-100/75 dark:bg-stone-900/75",
|
||||
].join(" ")}
|
||||
>
|
||||
<button
|
||||
onClick={() => {
|
||||
maybeScrollable()?.scrollBy({
|
||||
left: Math.floor(
|
||||
maybeScrollable()!.clientWidth *
|
||||
obj.scrollMultiplier *
|
||||
0.75,
|
||||
),
|
||||
behavior: "smooth",
|
||||
});
|
||||
}}
|
||||
class="border-light rounded-full border bg-stone-100 p-0.5 shadow transition hover:scale-110 active:scale-100 dark:bg-stone-900"
|
||||
>
|
||||
<Dynamic
|
||||
component={obj.chevronIcon}
|
||||
class={[`size-5 ${obj.iconPadding}`]}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</Show>
|
||||
<div
|
||||
class={[
|
||||
obj.gradientDirection,
|
||||
"h-full w-8 from-stone-100/75 to-transparent dark:from-stone-900/75",
|
||||
].join(" ")}
|
||||
/>
|
||||
</div>
|
||||
</Show>
|
||||
)}
|
||||
</For>
|
||||
|
||||
<div
|
||||
ref={maybeScrollable.set}
|
||||
onScroll={checkArrows}
|
||||
class={classPropToString([
|
||||
"no-scrollbar flex w-full overflow-x-auto",
|
||||
classes,
|
||||
])}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -44,10 +44,16 @@ export function SearchFrame({
|
||||
...config,
|
||||
});
|
||||
|
||||
const haystack = presets.list.map(
|
||||
(preset) =>
|
||||
`${preset.title}\t/ ${[...preset.path.map(({ name }) => name), preset.name].join(" / ")}`,
|
||||
);
|
||||
let haystack = [] as string[];
|
||||
|
||||
function initHaystackIfNeeded() {
|
||||
if (haystack.length) return;
|
||||
|
||||
haystack = presets.list.map(
|
||||
(preset) =>
|
||||
`${preset.title}\t/ ${[...preset.path.map(({ name }) => name), preset.name].join(" / ")}`,
|
||||
);
|
||||
}
|
||||
|
||||
const searchResult = createMemo(() => {
|
||||
scrollIntoView(counterRef());
|
||||
@@ -135,7 +141,7 @@ export function SearchFrame({
|
||||
</p>
|
||||
|
||||
<Show when={search()}>
|
||||
<div class="-mx-4 border-t border-orange-200/10" />
|
||||
<div class="border-lighter -mx-4 border-t" />
|
||||
|
||||
<div
|
||||
class="py-1"
|
||||
@@ -174,9 +180,10 @@ export function SearchFrame({
|
||||
class="w-full bg-transparent p-1 caret-orange-500 placeholder:text-orange-200/50 focus:outline-none"
|
||||
placeholder="Search by name or path"
|
||||
value={search()}
|
||||
onFocus={initHaystackIfNeeded}
|
||||
onInput={(event) => search.set(event.target.value)}
|
||||
/>
|
||||
<span class="-mx-1 flex size-5 flex-none items-center justify-center rounded-md border border-white text-xs font-bold">
|
||||
<span class="-mx-1 flex size-5 flex-none items-center justify-center rounded-md border border-current text-xs font-bold">
|
||||
<IconTablerSlash />
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -1,41 +1,257 @@
|
||||
import { version } from "/src/../package.json";
|
||||
import { ipad, iphone, macOS, safariOnly, standalone } from "/src/env";
|
||||
import { classPropToString } from "/src/solid/classes";
|
||||
|
||||
import { AnchorAPI } from "../strip/components/anchorAPI";
|
||||
import { AnchorGeyser } from "../strip/components/anchorGeyser";
|
||||
import { AnchorGit } from "../strip/components/anchorGit";
|
||||
import { AnchorNostr } from "../strip/components/anchorNostr";
|
||||
import { Header } from "./header";
|
||||
|
||||
export function SettingsFrame({
|
||||
marquee,
|
||||
selectedFrame,
|
||||
appTheme,
|
||||
backgroundMode,
|
||||
backgroundOpacity,
|
||||
}: {
|
||||
marquee: RWS<boolean>;
|
||||
selectedFrame: Accessor<FrameName>;
|
||||
appTheme: SL<"System" | "Dark" | "Light">;
|
||||
backgroundMode: SL<"Scroll" | "Static">;
|
||||
backgroundOpacity: SL<{ text: string; value: number }>;
|
||||
}) {
|
||||
const value = marquee();
|
||||
|
||||
return (
|
||||
<div class="flex-1 overflow-y-auto" hidden={selectedFrame() !== "Settings"}>
|
||||
<div
|
||||
class="flex-1 overflow-y-auto"
|
||||
style={{
|
||||
display: selectedFrame() !== "Settings" ? "none" : undefined,
|
||||
}}
|
||||
>
|
||||
<div class="space-y-4 p-4">
|
||||
<Header title="Settings" />
|
||||
<Header title="Settings">And other stuff</Header>
|
||||
|
||||
<div class="-mx-4 border-t border-orange-200/10" />
|
||||
<div class="border-lighter -mx-4 border-t" />
|
||||
|
||||
<div class="space-y-2">
|
||||
<p>Background</p>
|
||||
<div>Opacity</div>
|
||||
<div>
|
||||
<label class="switch">
|
||||
Scroll
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={value}
|
||||
onChange={(event) => marquee.set(event.target.checked || false)}
|
||||
/>
|
||||
<span class="slider"></span>
|
||||
</label>
|
||||
<div class="space-y-4">
|
||||
<Title>General</Title>
|
||||
|
||||
<RadioGroup
|
||||
title="Theme"
|
||||
ariaTitle="App's theme"
|
||||
description="Options for the app's theme"
|
||||
sl={appTheme}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="border-lighter -mx-4 border-t" />
|
||||
|
||||
<div class="space-y-4">
|
||||
<Title>Background</Title>
|
||||
|
||||
<RadioGroup
|
||||
title="Mode"
|
||||
ariaTitle="Background mode"
|
||||
description="Options for how the background in displayed"
|
||||
sl={backgroundMode}
|
||||
/>
|
||||
|
||||
<RadioGroup
|
||||
title="Opacity"
|
||||
ariaTitle="Background mode"
|
||||
description="Options for the opacity of the text in the background"
|
||||
sl={backgroundOpacity}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<hr class="border-lighter -mx-4 border-t" />
|
||||
|
||||
<div class="space-y-4">
|
||||
<Title>Donations</Title>
|
||||
|
||||
<p>
|
||||
A <strong>massive thank you</strong> to everybody who sent their
|
||||
hard earned sats. This project, by being completely free, is very
|
||||
dependent and only founded by the goodwill of fellow ₿itcoiners.
|
||||
</p>
|
||||
<p>Top 10 Leaderboard:</p>
|
||||
<ol class="list-inside list-decimal">
|
||||
<For
|
||||
each={[
|
||||
{
|
||||
name: "_Checkɱate",
|
||||
url: "https://primal.net/p/npub1qh5sal68c8swet6ut0w5evjmj6vnw29x3k967h7atn45unzjyeyq6ceh9r",
|
||||
amount: 500_000,
|
||||
},
|
||||
{
|
||||
name: "avvi |",
|
||||
url: "https://primal.net/p/npub1md2q6fexrtmd5hx9gw2p5640vg662sjlpxyz3tdmu4j4g8hhkm6scn6hx3",
|
||||
amount: 5_000,
|
||||
},
|
||||
{
|
||||
name: "mutatrum",
|
||||
url: "https://primal.net/p/npub1hklphk7fkfdgmzwclkhshcdqmnvr0wkfdy04j7yjjqa9lhvxuflsa23u2k",
|
||||
amount: 5_000,
|
||||
},
|
||||
{
|
||||
name: "Gunnar",
|
||||
url: "https://primal.net/p/npub1rx9wg2d5lhah45xst3580sajcld44m0ll9u5dqhu2t74p6xwufaqwghtd4",
|
||||
amount: 1_000,
|
||||
},
|
||||
{
|
||||
name: "Blokchain Boog",
|
||||
url: "https://x.com/BlokchainB",
|
||||
amount: 1_500 + 1590,
|
||||
},
|
||||
{
|
||||
name: "Josh",
|
||||
url: "https://primal.net/p/npub1pc57ls4rad5kvsp733suhzl2d4u9y7h4upt952a2pucnalc59teq33dmza",
|
||||
amount: 1_000,
|
||||
},
|
||||
{
|
||||
name: "Alp",
|
||||
url: "https://primal.net/p/npub175nul9cvufswwsnpy99lvyhg7ad9nkccxhkhusznxfkr7e0zxthql9g6w0",
|
||||
amount: 1_000,
|
||||
},
|
||||
{
|
||||
name: "Ulysses",
|
||||
url: "https://primal.net/p/npub1n7n3dssm90hfsfjtamwh2grpzwjlvd2yffae9pqgg99583lxdypsnn9gtv",
|
||||
amount: 1_000,
|
||||
},
|
||||
{
|
||||
name: "btcschellingpt",
|
||||
url: "https://primal.net/p/npub1nvfgglea9zlcs58tcqlc6j26rt50ngkgdk7699wfq4txrx37aqcsz4e7zd",
|
||||
amount: 1_000,
|
||||
},
|
||||
{
|
||||
name: "Coinatra",
|
||||
url: "https://primal.net/p/npub1eut9kcejweegwp9waq3a4g03pvprdzkzvjjvl8fvj2a2wlx030eswzfna8",
|
||||
amount: 1_000,
|
||||
},
|
||||
{
|
||||
name: "Printer Go Brrrr",
|
||||
url: "https://primal.net/p/npub1l5pxvjzhw77h86tu0sml2gxg8jpwxch7fsj6d05n7vuqpq75v34syk4q0n",
|
||||
amount: 1_000,
|
||||
},
|
||||
{
|
||||
name: "b81776c32d7b",
|
||||
url: "https://primal.net/p/npub1hqthdsed0wpg57sqsc5mtyqxxgrh3s7493ja5h49v23v2nhhds4qk4w0kz",
|
||||
amount: 17_509,
|
||||
},
|
||||
]
|
||||
.sort((a, b) =>
|
||||
b.amount !== a.amount
|
||||
? b.amount - a.amount
|
||||
: a.name.localeCompare(b.name),
|
||||
)
|
||||
.slice(0, 10)}
|
||||
>
|
||||
{({ name, url, amount }, index) => (
|
||||
<li>
|
||||
<a href={url} target="_blank">
|
||||
{name}
|
||||
</a>{" "}
|
||||
- {amount.toLocaleString("en-us")} sats
|
||||
</li>
|
||||
)}
|
||||
</For>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<Show when={!standalone && safariOnly && (macOS || ipad || iphone)}>
|
||||
<hr class="border-lighter -mx-4 border-t" />
|
||||
|
||||
<div class="space-y-4">
|
||||
<Title>Install</Title>
|
||||
<p>
|
||||
<Show when={macOS}>
|
||||
This app can be installed by clicking on the "File" tab on the
|
||||
menu bar and then on "Add to dock".
|
||||
</Show>
|
||||
<Show when={iphone || ipad}>
|
||||
This app can be installed by tapping on the "Share" button tab
|
||||
of Safari and then on "Add to Home Screen".
|
||||
</Show>
|
||||
</p>
|
||||
</div>
|
||||
<hr class="border-t border-orange-200/20" />
|
||||
<p>Version: {version}</p>
|
||||
</Show>
|
||||
</div>
|
||||
|
||||
<hr class="border-lighter -mx-4 border-t" />
|
||||
|
||||
<div class="pt-4 md:hidden">
|
||||
<div class="flex items-center justify-center gap-8 py-1">
|
||||
<AnchorAPI />
|
||||
<AnchorGit />
|
||||
<AnchorNostr />
|
||||
<AnchorGeyser />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="pb-[10vh] pt-4 text-center">
|
||||
<span class="opacity-50">Version:</span>{" "}
|
||||
<a
|
||||
href="https://github.com/satonomics-org/satonomics/blob/main/CHANGELOG.md"
|
||||
target="_blank"
|
||||
>
|
||||
{version}
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Title({ children }: ParentProps) {
|
||||
return <p class="text-base font-medium">{children}</p>;
|
||||
}
|
||||
|
||||
function RadioGroup<
|
||||
T extends
|
||||
| string
|
||||
| {
|
||||
text: string;
|
||||
value: number;
|
||||
},
|
||||
>({
|
||||
title,
|
||||
sl,
|
||||
ariaTitle,
|
||||
description,
|
||||
}: {
|
||||
title: string;
|
||||
ariaTitle: string;
|
||||
description: string;
|
||||
sl: SL<T>;
|
||||
}) {
|
||||
return (
|
||||
<fieldset aria-label={`Choose an option for: ${ariaTitle}`}>
|
||||
<p class="pb-0.5">{title}</p>
|
||||
|
||||
<p class="pb-1 text-sm opacity-50">{description}</p>
|
||||
|
||||
<div class="border-superlight -mx-2 mt-2 flex gap-1.5 rounded-lg border bg-stone-400/30 p-1.5 backdrop-blur-[2px] dark:bg-stone-950/75">
|
||||
<For each={sl.list()}>
|
||||
{(value) => (
|
||||
<label
|
||||
class={classPropToString([
|
||||
value === sl.selected()
|
||||
? "border-lighter bg-orange-50/75 shadow dark:bg-orange-200/10"
|
||||
: "border-transparent",
|
||||
"flex flex-1 cursor-pointer select-none items-center justify-center rounded-md border px-3 py-1.5 font-medium hover:bg-orange-50 focus:outline-none active:scale-95 active:bg-orange-50 dark:hover:bg-orange-200/20 dark:active:bg-orange-200/10",
|
||||
])}
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
name={`${title}-option`}
|
||||
value={typeof value === "object" ? value.value : value}
|
||||
class="sr-only"
|
||||
onClick={() => {
|
||||
sl.select(value);
|
||||
}}
|
||||
/>
|
||||
<span>{typeof value === "object" ? value.text : value}</span>
|
||||
</label>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
</fieldset>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,117 +0,0 @@
|
||||
import { File } from "./file";
|
||||
import { Folder } from "./folder";
|
||||
|
||||
export function Tree({
|
||||
tree,
|
||||
selected,
|
||||
openedFolders,
|
||||
depth = 0,
|
||||
visible,
|
||||
selectPreset,
|
||||
path = [],
|
||||
favorites,
|
||||
}: {
|
||||
tree: PresetTree;
|
||||
selected: Accessor<Preset>;
|
||||
selectPreset(preset: Preset): void;
|
||||
openedFolders: RWS<Set<string>>;
|
||||
depth?: number;
|
||||
visible?: Accessor<boolean>;
|
||||
path?: FilePath;
|
||||
favorites: Accessor<Preset[]>;
|
||||
}) {
|
||||
return (
|
||||
<div style={{ display: visible?.() === false ? "none" : undefined }}>
|
||||
<For each={tree}>
|
||||
{(thing) => {
|
||||
const active = createMemo(() => thing.id === selected().id);
|
||||
const favorite = createMemo(() =>
|
||||
favorites().includes(thing as Preset),
|
||||
);
|
||||
const visited = (thing as Preset).visited;
|
||||
|
||||
if (!("tree" in thing)) {
|
||||
return (
|
||||
<File
|
||||
id={thing.id}
|
||||
name={thing.name}
|
||||
active={active}
|
||||
depth={depth}
|
||||
icon={thing.icon || IconTablerFile}
|
||||
favorite={favorite}
|
||||
visited={visited}
|
||||
onClick={() => {
|
||||
const selectedId = selected().id;
|
||||
|
||||
if (selectedId === thing.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Has been filled in createPresets
|
||||
selectPreset(thing as Preset);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const childrenVisible = createMemo(() =>
|
||||
openedFolders().has(thing.id),
|
||||
);
|
||||
|
||||
const childCount = countChildren(thing);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Folder
|
||||
id={thing.id}
|
||||
name={thing.name}
|
||||
depth={depth}
|
||||
open={childrenVisible}
|
||||
children={childCount}
|
||||
onClick={() => {
|
||||
openedFolders.set((s) => {
|
||||
if (childrenVisible()) {
|
||||
s.delete(thing.id);
|
||||
} else {
|
||||
s.add(thing.id);
|
||||
}
|
||||
|
||||
return s;
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<Tree
|
||||
tree={thing.tree}
|
||||
selected={selected}
|
||||
depth={depth + 1}
|
||||
openedFolders={openedFolders}
|
||||
visible={childrenVisible}
|
||||
path={[...path, { name: thing.name, id: thing.id }]}
|
||||
selectPreset={selectPreset}
|
||||
favorites={favorites}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
</For>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function countChildren(folder: PresetFolder) {
|
||||
let count = 0;
|
||||
|
||||
function _countChildren(tree: PartialPresetTree) {
|
||||
tree.forEach((anyPreset) => {
|
||||
if ("tree" in anyPreset) {
|
||||
_countChildren(anyPreset.tree);
|
||||
} else {
|
||||
count += 1;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
_countChildren(folder.tree);
|
||||
|
||||
return count;
|
||||
}
|
||||
@@ -1,25 +1,34 @@
|
||||
import { touchScreen } from "/src/env";
|
||||
|
||||
export function Qrcode({ qrcode }: { qrcode: RWS<string> }) {
|
||||
return (
|
||||
<Show when={qrcode()}>
|
||||
<div
|
||||
class="absolute inset-0 z-50 flex size-full justify-center bg-black"
|
||||
class="absolute inset-0 z-50 flex size-full items-center justify-center bg-black/50 backdrop-blur-md"
|
||||
onClick={() => {
|
||||
qrcode.set("");
|
||||
}}
|
||||
>
|
||||
<div class="flex size-full max-w-md flex-col items-center justify-center bg-black px-8 py-16 text-lg">
|
||||
<p class="pb-16 text-2xl font-bold">Share</p>
|
||||
<div class="flex size-full max-h-[80dvh] max-w-md flex-col justify-center space-y-8 px-8 pb-8 text-base">
|
||||
<p class="pb-4 text-center text-3xl font-bold">Share</p>
|
||||
|
||||
<div class="flex min-h-0 w-full flex-1 flex-col">
|
||||
<p>You can scan the following QR Code with a phone:</p>
|
||||
<p>
|
||||
To share this page, you can either send the following QR Code with a
|
||||
phone:
|
||||
</p>
|
||||
<div class="flex min-h-0 w-full flex-1 flex-col items-center justify-center">
|
||||
<img
|
||||
class="aspect-square min-h-0 flex-1 grow object-contain"
|
||||
onClick={(event) => {
|
||||
event?.stopPropagation();
|
||||
}}
|
||||
src={qrcode()}
|
||||
style={{ "image-rendering": "pixelated" }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p>Or if you prefer you can send this link instead:</p>
|
||||
<p>Or if you prefer you can share this link instead:</p>
|
||||
<a
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
@@ -29,6 +38,11 @@ export function Qrcode({ qrcode }: { qrcode: RWS<string> }) {
|
||||
{location.href}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<p>
|
||||
{touchScreen ? "Touch" : "Click"} anywhere but on the QR Code to
|
||||
exit.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
@@ -4,22 +4,25 @@ export function AnchorAPI() {
|
||||
return (
|
||||
<Anchor
|
||||
title="API"
|
||||
icon={() => () => (
|
||||
<svg
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M5.13468 2.41153C3.88395 3.0478 3.37143 3.79772 3.37143 4.4186C3.37143 5.03949 3.88395 5.78941 5.13468 6.42568C6.3444 7.04109 8.06359 7.44186 10 7.44186C11.9364 7.44186 13.6556 7.04109 14.8653 6.42568C16.1161 5.78941 16.6286 5.03949 16.6286 4.4186C16.6286 3.79772 16.1161 3.0478 14.8653 2.41153C13.6556 1.79612 11.9364 1.39535 10 1.39535C8.06359 1.39535 6.3444 1.79612 5.13468 2.41153ZM16.6286 6.93694C16.2841 7.21648 15.8934 7.46274 15.4786 7.67372C14.0411 8.40502 12.1032 8.83721 10 8.83721C7.89684 8.83721 5.95889 8.40502 4.52136 7.67372C4.10664 7.46274 3.71588 7.21648 3.37143 6.93694V10C3.37143 10.6209 3.88395 11.3708 5.13468 12.0071C6.3444 12.6225 8.06359 13.0233 10 13.0233C11.9364 13.0233 13.6556 12.6225 14.8653 12.0071C16.1161 11.3708 16.6286 10.6209 16.6286 10V6.93694ZM18 4.4186C18 2.98447 16.8752 1.87393 15.4786 1.16349C14.0411 0.432186 12.1032 0 10 0C7.89684 0 5.95889 0.432186 4.52136 1.16349C3.12484 1.87393 2 2.98447 2 4.4186V15.5814C2 17.0155 3.12484 18.1261 4.52136 18.8365C5.95889 19.5678 7.89684 20 10 20C12.1032 20 14.0411 19.5678 15.4786 18.8365C16.8752 18.1261 18 17.0155 18 15.5814V4.4186ZM16.6286 12.5183C16.2841 12.7979 15.8934 13.0441 15.4786 13.2551C14.0411 13.9864 12.1032 14.4186 10 14.4186C7.89684 14.4186 5.95889 13.9864 4.52136 13.2551C4.10664 13.0441 3.71588 12.7979 3.37143 12.5183V15.5814C3.37143 16.2023 3.88395 16.9522 5.13468 17.5885C6.3444 18.2039 8.06359 18.6047 10 18.6047C11.9364 18.6047 13.6556 18.2039 14.8653 17.5885C16.1161 16.9522 16.6286 16.2023 16.6286 15.5814V12.5183ZM6.34285 10C6.34285 10.5138 5.93351 10.9302 5.42857 10.9302C4.92362 10.9302 4.51428 10.5138 4.51428 10C4.51428 9.48625 4.92362 9.06977 5.42857 9.06977C5.93351 9.06977 6.34285 9.48625 6.34285 10ZM9.0857 11.8605C9.59065 11.8605 9.99999 11.444 9.99999 10.9302C9.99999 10.4165 9.59065 10 9.0857 10C8.58076 10 8.17142 10.4165 8.17142 10.9302C8.17142 11.444 8.58076 11.8605 9.0857 11.8605ZM6.34285 15.5814C6.34285 16.0951 5.93351 16.5116 5.42857 16.5116C4.92362 16.5116 4.51428 16.0951 4.51428 15.5814C4.51428 15.0676 4.92362 14.6512 5.42857 14.6512C5.93351 14.6512 6.34285 15.0676 6.34285 15.5814ZM9.0857 17.4419C9.59065 17.4419 9.99999 17.0254 9.99999 16.5116C9.99999 15.9979 9.59065 15.5814 9.0857 15.5814C8.58076 15.5814 8.17142 15.9979 8.17142 16.5116C8.17142 17.0254 8.58076 17.4419 9.0857 17.4419Z"
|
||||
fill="currentColor"
|
||||
></path>
|
||||
</svg>
|
||||
)}
|
||||
icon={
|
||||
() => IconTablerApi
|
||||
// () => (
|
||||
// <svg
|
||||
// width="20"
|
||||
// height="20"
|
||||
// viewBox="0 0 20 20"
|
||||
// fill="none"
|
||||
// xmlns="http://www.w3.org/2000/svg"
|
||||
// >
|
||||
// <path
|
||||
// fill-rule="evenodd"
|
||||
// clip-rule="evenodd"
|
||||
// d="M5.13468 2.41153C3.88395 3.0478 3.37143 3.79772 3.37143 4.4186C3.37143 5.03949 3.88395 5.78941 5.13468 6.42568C6.3444 7.04109 8.06359 7.44186 10 7.44186C11.9364 7.44186 13.6556 7.04109 14.8653 6.42568C16.1161 5.78941 16.6286 5.03949 16.6286 4.4186C16.6286 3.79772 16.1161 3.0478 14.8653 2.41153C13.6556 1.79612 11.9364 1.39535 10 1.39535C8.06359 1.39535 6.3444 1.79612 5.13468 2.41153ZM16.6286 6.93694C16.2841 7.21648 15.8934 7.46274 15.4786 7.67372C14.0411 8.40502 12.1032 8.83721 10 8.83721C7.89684 8.83721 5.95889 8.40502 4.52136 7.67372C4.10664 7.46274 3.71588 7.21648 3.37143 6.93694V10C3.37143 10.6209 3.88395 11.3708 5.13468 12.0071C6.3444 12.6225 8.06359 13.0233 10 13.0233C11.9364 13.0233 13.6556 12.6225 14.8653 12.0071C16.1161 11.3708 16.6286 10.6209 16.6286 10V6.93694ZM18 4.4186C18 2.98447 16.8752 1.87393 15.4786 1.16349C14.0411 0.432186 12.1032 0 10 0C7.89684 0 5.95889 0.432186 4.52136 1.16349C3.12484 1.87393 2 2.98447 2 4.4186V15.5814C2 17.0155 3.12484 18.1261 4.52136 18.8365C5.95889 19.5678 7.89684 20 10 20C12.1032 20 14.0411 19.5678 15.4786 18.8365C16.8752 18.1261 18 17.0155 18 15.5814V4.4186ZM16.6286 12.5183C16.2841 12.7979 15.8934 13.0441 15.4786 13.2551C14.0411 13.9864 12.1032 14.4186 10 14.4186C7.89684 14.4186 5.95889 13.9864 4.52136 13.2551C4.10664 13.0441 3.71588 12.7979 3.37143 12.5183V15.5814C3.37143 16.2023 3.88395 16.9522 5.13468 17.5885C6.3444 18.2039 8.06359 18.6047 10 18.6047C11.9364 18.6047 13.6556 18.2039 14.8653 17.5885C16.1161 16.9522 16.6286 16.2023 16.6286 15.5814V12.5183ZM6.34285 10C6.34285 10.5138 5.93351 10.9302 5.42857 10.9302C4.92362 10.9302 4.51428 10.5138 4.51428 10C4.51428 9.48625 4.92362 9.06977 5.42857 9.06977C5.93351 9.06977 6.34285 9.48625 6.34285 10ZM9.0857 11.8605C9.59065 11.8605 9.99999 11.444 9.99999 10.9302C9.99999 10.4165 9.59065 10 9.0857 10C8.58076 10 8.17142 10.4165 8.17142 10.9302C8.17142 11.444 8.58076 11.8605 9.0857 11.8605ZM6.34285 15.5814C6.34285 16.0951 5.93351 16.5116 5.42857 16.5116C4.92362 16.5116 4.51428 16.0951 4.51428 15.5814C4.51428 15.0676 4.92362 14.6512 5.42857 14.6512C5.93351 14.6512 6.34285 15.0676 6.34285 15.5814ZM9.0857 17.4419C9.59065 17.4419 9.99999 17.0254 9.99999 16.5116C9.99999 15.9979 9.59065 15.5814 9.0857 15.5814C8.58076 15.5814 8.17142 15.9979 8.17142 16.5116C8.17142 17.0254 8.58076 17.4419 9.0857 17.4419Z"
|
||||
// fill="currentColor"
|
||||
// ></path>
|
||||
// </svg>
|
||||
// )
|
||||
}
|
||||
href="https://api.satonomics.xyz"
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import { Anchor } from "./anchor";
|
||||
|
||||
export function AnchorGeyser() {
|
||||
return (
|
||||
<Anchor
|
||||
title="Geyser"
|
||||
icon={() => (props: { class?: string }) => (
|
||||
<svg
|
||||
viewBox="0 0 365 365"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-hidden="true"
|
||||
class={props.class}
|
||||
style={{
|
||||
padding: "0.125rem",
|
||||
}}
|
||||
>
|
||||
<path
|
||||
d="M346.396 125.679C346.193 124.459 345.986 123.24 345.755 122.028C345.017 118.123 345.017 118.123 345.017 118.123C343.98 113.895 339.304 110.436 334.628 110.435L221.265 110.425C216.587 110.424 211.581 114.065 210.14 118.514L186.054 192.873C185.343 195.069 184.408 197.774 185.441 199.236C186.502 200.735 189.57 200.963 191.939 200.963H238.093C238.093 200.963 240.249 200.945 240.536 202.442C240.843 204.031 239.19 205.743 239.19 205.743L157.833 291.25C154.987 294.242 150.017 299.46 146.792 302.845L125.923 324.742C125.923 324.742 125.497 325.221 124.999 325.68C124.712 325.946 124.436 326.178 124.173 326.383C121.278 328.632 119.909 327.374 121.11 323.26L131.729 286.87C133.04 282.381 135.182 275.033 136.493 270.544L142.575 249.702C143.595 246.206 144.753 242.242 145.481 239.743C145.688 239.033 146.17 236.844 145.442 235.737C144.751 234.686 143.587 234.487 142.28 234.487C140.864 234.487 139.196 234.487 137.442 234.487H93.4839C88.8059 234.487 87.2359 232.119 89.9939 229.225C92.7519 226.331 97.6479 221.193 100.875 217.807L135.051 181.949C138.278 178.563 143.246 173.345 146.094 170.352C148.942 167.359 153.91 162.139 157.133 158.75L162.904 152.684C166.129 149.295 171.404 143.75 174.627 140.362L186.471 127.916C189.694 124.527 194.758 119.205 197.721 116.089C200.686 112.974 203.889 109.608 204.838 108.609C205.787 107.611 209.201 104.022 212.426 100.633L278.317 31.3812C281.542 27.9922 280.911 23.2282 276.919 20.7932C276.919 20.7932 267.599 15.1122 258.755 11.4192C235.769 1.81919 210.816 -1.59681 185.61 0.678194C178.503 1.32019 171.393 2.41119 164.321 3.94219C145.237 8.07619 126.546 15.3542 108.954 25.4422C97.9699 31.7382 87.4599 39.1132 77.5829 47.4572C54.3189 67.1072 34.7959 91.9432 20.9249 120.702C14.0339 134.988 8.95193 149.42 5.51493 163.704C-1.69007 193.646 -1.63107 223.046 4.49993 249.499C8.57593 267.102 15.3589 283.516 24.5589 298.092C25.1199 298.982 25.6939 299.867 26.2739 300.743C31.2249 308.225 36.8479 315.217 43.0999 321.638C52.6369 331.432 63.6919 339.937 76.2069 346.811C82.4469 350.24 88.8519 353.149 95.3729 355.575C99.0779 356.951 102.824 358.176 106.605 359.24C113.087 361.068 119.679 362.438 126.337 363.378C184.333 371.547 251.78 346.032 298.382 290.078C307.3 279.37 315.068 268.043 321.671 256.283C330.341 240.845 336.993 224.657 341.544 208.154C345.339 194.392 347.665 180.431 348.483 166.541C349.298 152.728 348.617 139.014 346.396 125.679Z"
|
||||
fill="currentColor"
|
||||
></path>
|
||||
</svg>
|
||||
)}
|
||||
href="https://geyser.fund/project/satonomics/"
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -4,8 +4,8 @@ export function AnchorGit() {
|
||||
return (
|
||||
<Anchor
|
||||
title="Git"
|
||||
icon={() => IconTablerGitMerge}
|
||||
href="https://codeberg.org/satonomics/satonomics"
|
||||
icon={() => IconTablerBrandGithubFilled}
|
||||
href="https://github.com/satonomics-org/satonomics"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
export function AnchorLogo() {
|
||||
return (
|
||||
<a
|
||||
class="inline-flex justify-center rounded-lg bg-gradient-to-br from-orange-500 to-orange-800 p-4 text-white"
|
||||
class="inline-flex justify-center rounded-lg bg-gradient-to-br from-orange-300 to-orange-600 p-4 text-orange-50 shadow dark:from-orange-500 dark:to-orange-800 dark:text-orange-50"
|
||||
href="https://app.satonomics.xyz"
|
||||
title="Reload"
|
||||
>
|
||||
|
||||
+2
-2
@@ -1,13 +1,13 @@
|
||||
import { Button } from "./button";
|
||||
|
||||
export function ButtonTree({
|
||||
export function ButtonFolders({
|
||||
selected,
|
||||
setSelected,
|
||||
}: {
|
||||
selected: Accessor<FrameName>;
|
||||
setSelected: Setter<FrameName>;
|
||||
}) {
|
||||
const frameName: FrameName = "Tree";
|
||||
const frameName: FrameName = "Folders";
|
||||
|
||||
return (
|
||||
<Button
|
||||
@@ -20,10 +20,10 @@ export function Clickable({
|
||||
class={classPropToString([
|
||||
!href
|
||||
? selected?.()
|
||||
? "bg-orange-200/10"
|
||||
: "text-orange-100/50"
|
||||
: "text-orange-300/70",
|
||||
"select-none rounded-lg p-3.5 hover:bg-orange-200/10 hover:text-orange-400 hover:opacity-100 active:scale-90",
|
||||
? "bg-orange-800/10 dark:bg-orange-200/10"
|
||||
: "text-orange-900/50 dark:text-orange-100/50"
|
||||
: "text-opacity-70 dark:text-opacity-70",
|
||||
"inline-flex select-none rounded-lg p-3.5 hover:bg-orange-800/10 hover:text-orange-600 hover:opacity-100 active:scale-90 dark:hover:bg-orange-200/10 dark:hover:text-orange-400",
|
||||
])}
|
||||
title={title}
|
||||
onClick={onClick}
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import { AnchorAPI } from "./components/anchorAPI";
|
||||
import { AnchorGeyser } from "./components/anchorGeyser";
|
||||
import { AnchorGit } from "./components/anchorGit";
|
||||
import { AnchorLogo } from "./components/anchorLogo";
|
||||
import { AnchorNostr } from "./components/anchorNostr";
|
||||
import { ButtonChart } from "./components/buttonChart";
|
||||
import { ButtonFavorites } from "./components/buttonFavorites";
|
||||
import { ButtonFolders } from "./components/buttonFolders";
|
||||
import { ButtonHistory } from "./components/buttonHistory";
|
||||
import { ButtonSearch } from "./components/buttonSearch";
|
||||
import { ButtonSettings } from "./components/buttonSettings";
|
||||
import { ButtonTree } from "./components/buttonTree";
|
||||
|
||||
export function StripDesktop({
|
||||
selected,
|
||||
@@ -20,7 +21,7 @@ export function StripDesktop({
|
||||
<>
|
||||
<AnchorLogo />
|
||||
|
||||
<ButtonTree selected={selected} setSelected={setSelected} />
|
||||
<ButtonFolders selected={selected} setSelected={setSelected} />
|
||||
<ButtonFavorites selected={selected} setSelected={setSelected} />
|
||||
<ButtonSearch selected={selected} setSelected={setSelected} />
|
||||
<ButtonHistory selected={selected} setSelected={setSelected} />
|
||||
@@ -32,6 +33,7 @@ export function StripDesktop({
|
||||
<AnchorAPI />
|
||||
<AnchorGit />
|
||||
<AnchorNostr />
|
||||
<AnchorGeyser />
|
||||
{/* <AnchorHome /> */}
|
||||
</>
|
||||
);
|
||||
@@ -47,7 +49,7 @@ export function StripMobile({
|
||||
return (
|
||||
<>
|
||||
<ButtonChart selected={selected} setSelected={setSelected} />
|
||||
<ButtonTree selected={selected} setSelected={setSelected} />
|
||||
<ButtonFolders selected={selected} setSelected={setSelected} />
|
||||
<ButtonFavorites selected={selected} setSelected={setSelected} />
|
||||
<ButtonSearch selected={selected} setSelected={setSelected} />
|
||||
<ButtonHistory selected={selected} setSelected={setSelected} />
|
||||
|
||||
@@ -62,12 +62,12 @@ export function Update() {
|
||||
<Show when={needRefresh()}>
|
||||
<div class="absolute inset-x-1.5 top-1.5 z-[99999] flex items-center justify-between rounded-lg bg-orange-700/75 p-1.5 shadow backdrop-blur-sm">
|
||||
<div>
|
||||
<span class="truncate px-1">New version available, please</span>
|
||||
<span class="truncate px-1">New version available,</span>
|
||||
<button
|
||||
class="mr-2 rounded-md bg-orange-50 bg-opacity-60 px-1.5 py-0.5 font-medium text-orange-950 hover:bg-opacity-100"
|
||||
onClick={async () => await updateServiceWorker()}
|
||||
>
|
||||
install
|
||||
Install
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
|
||||
+114
-44
@@ -1,12 +1,9 @@
|
||||
import { createRWS } from "/src/solid/rws";
|
||||
|
||||
import { env } from "../env";
|
||||
import { standalone } from "../env";
|
||||
import { createDatasets } from "../scripts/datasets";
|
||||
import { chartState } from "../scripts/lightweightCharts/chart/state";
|
||||
import { setTimeScale } from "../scripts/lightweightCharts/chart/time";
|
||||
import { createPresets } from "../scripts/presets";
|
||||
import { priceToUSLocale } from "../scripts/utils/locale";
|
||||
import { run } from "../scripts/utils/run";
|
||||
import { createSL } from "../scripts/utils/selectableList/static";
|
||||
import { sleep } from "../scripts/utils/sleep";
|
||||
import {
|
||||
readBooleanFromStorage,
|
||||
@@ -15,13 +12,12 @@ import {
|
||||
import { readBooleanURLParam, writeURLParam } from "../scripts/utils/urlParams";
|
||||
import { webSockets } from "../scripts/ws";
|
||||
import { classPropToString } from "../solid/classes";
|
||||
import { Background, LOCAL_STORAGE_MARQUEE_KEY } from "./components/background";
|
||||
import { Background } from "./components/background";
|
||||
import { ChartFrame } from "./components/frames/chart";
|
||||
import { FavoritesFrame } from "./components/frames/favorites";
|
||||
import { FoldersFrame } from "./components/frames/folders";
|
||||
import { HistoryFrame } from "./components/frames/history";
|
||||
import { SearchFrame } from "./components/frames/search";
|
||||
import { SettingsFrame } from "./components/frames/settings";
|
||||
import { TreeFrame } from "./components/frames/tree";
|
||||
import { Qrcode } from "./components/qrcode";
|
||||
import { StripDesktop, StripMobile } from "./components/strip";
|
||||
import { Update } from "./components/update";
|
||||
@@ -33,9 +29,91 @@ export const INPUT_PRESET_SEARCH_ID = "input-search-preset";
|
||||
|
||||
export function App() {
|
||||
const tabFocused = createRWS(true);
|
||||
|
||||
const qrcode = createRWS("");
|
||||
|
||||
const appTheme = createSL(["System", "Dark", "Light"] as const, {
|
||||
saveable: {
|
||||
key: "app-theme",
|
||||
mode: "localStorage",
|
||||
},
|
||||
defaultIndex: 0,
|
||||
});
|
||||
|
||||
const dark = createRWS(false);
|
||||
|
||||
const preferredColorSchemeMatchMedia = window.matchMedia(
|
||||
"(prefers-color-scheme: dark)",
|
||||
);
|
||||
|
||||
const preferredSystemTheme = createRWS<"light" | "dark">(
|
||||
preferredColorSchemeMatchMedia.matches ? "dark" : "light",
|
||||
);
|
||||
|
||||
function preferredColorSchemeListener(event: MediaQueryListEvent) {
|
||||
return preferredSystemTheme.set(event.matches ? "dark" : "light");
|
||||
}
|
||||
|
||||
preferredColorSchemeMatchMedia.addEventListener(
|
||||
"change",
|
||||
preferredColorSchemeListener,
|
||||
);
|
||||
|
||||
onCleanup(() => {
|
||||
preferredColorSchemeMatchMedia.removeEventListener(
|
||||
"change",
|
||||
preferredColorSchemeListener,
|
||||
);
|
||||
});
|
||||
|
||||
createEffect(() => {
|
||||
if (
|
||||
appTheme.selected() === "Dark" ||
|
||||
(appTheme.selected() === "System" && preferredSystemTheme() === "dark")
|
||||
) {
|
||||
dark.set(true);
|
||||
document.documentElement.classList.add("dark");
|
||||
} else {
|
||||
dark.set(false);
|
||||
document.documentElement.classList.remove("dark");
|
||||
}
|
||||
});
|
||||
|
||||
const backgroundMode = createSL(["Scroll", "Static"] as const, {
|
||||
saveable: {
|
||||
key: "bg-mode",
|
||||
mode: "localStorage",
|
||||
},
|
||||
defaultIndex: 0,
|
||||
});
|
||||
|
||||
const backgroundOpacity = createSL(
|
||||
[
|
||||
{
|
||||
text: "Strong",
|
||||
value: 0.0444,
|
||||
},
|
||||
{
|
||||
text: "Normal",
|
||||
value: 0.0333,
|
||||
},
|
||||
{
|
||||
text: "Light",
|
||||
value: 0.0222,
|
||||
},
|
||||
{
|
||||
text: "Subtle",
|
||||
value: 0.0111,
|
||||
},
|
||||
] as const,
|
||||
{
|
||||
saveable: {
|
||||
key: "bg-text-opacity",
|
||||
mode: "localStorage",
|
||||
},
|
||||
defaultIndex: 2,
|
||||
},
|
||||
);
|
||||
|
||||
const fullscreen = createRWS(
|
||||
readBooleanURLParam(LOCAL_STORAGE_FULLSCREEN) ||
|
||||
readBooleanFromStorage(LOCAL_STORAGE_FULLSCREEN) ||
|
||||
@@ -50,7 +128,7 @@ export function App() {
|
||||
window.addEventListener("resize", windowResizeCallback);
|
||||
onCleanup(() => window.removeEventListener("resize", windowResizeCallback));
|
||||
|
||||
const windowSizeIsAtLeastMedium = createMemo(() => windowWidth() >= 720);
|
||||
const windowSizeIsAtLeastMedium = createMemo(() => windowWidth() >= 768);
|
||||
|
||||
const minBarWidth = 384;
|
||||
const barWidth = createRWS(
|
||||
@@ -75,14 +153,12 @@ export function App() {
|
||||
|
||||
const selectedFrame = createMemo(() =>
|
||||
windowSizeIsAtLeastMedium() && _selectedFrame() === "Chart"
|
||||
? "Tree"
|
||||
? "Folders"
|
||||
: _selectedFrame(),
|
||||
);
|
||||
|
||||
const presets = createPresets();
|
||||
|
||||
const marquee = createRWS(!localStorage.getItem(LOCAL_STORAGE_MARQUEE_KEY));
|
||||
|
||||
const resizingBarStart = createRWS<number | undefined>(undefined);
|
||||
const resizingBarWidth = createRWS<number>(0);
|
||||
|
||||
@@ -97,15 +173,7 @@ export function App() {
|
||||
},
|
||||
);
|
||||
|
||||
const activeResources = createRWS<Set<ResourceDataset<any, any>>>(new Set(), {
|
||||
equals: false,
|
||||
});
|
||||
|
||||
// Can't put datasets inside a signal as it breaks lazy memo
|
||||
|
||||
const datasets = createDatasets({
|
||||
setActiveResources: activeResources.set,
|
||||
});
|
||||
const datasets = createDatasets();
|
||||
|
||||
onMount(() => {
|
||||
webSockets.openAll();
|
||||
@@ -118,7 +186,7 @@ export function App() {
|
||||
|
||||
console.log("close:", close);
|
||||
|
||||
document.title = `${priceToUSLocale(latest.close, false)} | Satonomics`;
|
||||
document.title = `${latest.close.toLocaleString("en-us")} | Satonomics`;
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -157,11 +225,19 @@ export function App() {
|
||||
document.addEventListener("keydown", documentOnKeyDown);
|
||||
onCleanup(() => document.removeEventListener("keydown", documentOnKeyDown));
|
||||
|
||||
const resizeInitialRange = createRWS<TimeRange | null>(null);
|
||||
const SearchFrame = lazy(() =>
|
||||
import("./components/frames/search").then((d) => ({
|
||||
default: d.SearchFrame,
|
||||
})),
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Background marquee={marquee} focused={tabFocused} />
|
||||
<Background
|
||||
focused={tabFocused}
|
||||
mode={backgroundMode}
|
||||
opacity={backgroundOpacity}
|
||||
/>
|
||||
|
||||
<div
|
||||
class="relative h-dvh selection:bg-orange-800"
|
||||
@@ -181,8 +257,6 @@ export function App() {
|
||||
windowWidth60p(),
|
||||
),
|
||||
);
|
||||
|
||||
setTimeScale(resizeInitialRange());
|
||||
}
|
||||
}}
|
||||
onMouseUp={() => resizingBarStart.set(undefined)}
|
||||
@@ -193,15 +267,15 @@ export function App() {
|
||||
<Qrcode qrcode={qrcode} />
|
||||
<Update />
|
||||
|
||||
<div class="flex size-full flex-col md:flex-row md:p-3">
|
||||
<div class="flex size-full flex-col md:flex-row md:p-3 md:short:p-0">
|
||||
<Show when={!windowSizeIsAtLeastMedium() || !fullscreen()}>
|
||||
<div
|
||||
class={classPropToString([
|
||||
env.standalone && "border-t",
|
||||
"flex h-full flex-col overflow-hidden border-white/10 bg-gradient-to-b from-orange-500/10 to-orange-950/10 md:flex-row md:rounded-2xl md:border",
|
||||
standalone && "border-t md:border-t-0",
|
||||
"border-lighter flex h-full flex-col overflow-hidden bg-gradient-to-b from-orange-300/15 to-orange-400/15 dark:from-orange-500/10 dark:to-orange-950/10 md:flex-row md:rounded-2xl md:border md:shadow-md md:short:hidden",
|
||||
])}
|
||||
>
|
||||
<div class="hidden flex-col gap-2 border-r border-white/10 bg-black/30 p-3 backdrop-blur-sm md:flex">
|
||||
<div class="border-lighter hidden flex-col gap-2 border-r bg-orange-300/30 p-3 backdrop-blur-sm dark:bg-black/30 md:flex">
|
||||
<StripDesktop
|
||||
selected={selectedFrame}
|
||||
setSelected={_selectedFrame.set}
|
||||
@@ -226,11 +300,11 @@ export function App() {
|
||||
qrcode={qrcode}
|
||||
standalone={false}
|
||||
datasets={datasets}
|
||||
activeResources={activeResources}
|
||||
dark={dark}
|
||||
/>
|
||||
</Show>
|
||||
|
||||
<TreeFrame presets={presets} selectedFrame={selectedFrame} />
|
||||
<FoldersFrame presets={presets} selectedFrame={selectedFrame} />
|
||||
<FavoritesFrame
|
||||
presets={presets}
|
||||
selectedFrame={selectedFrame}
|
||||
@@ -238,15 +312,17 @@ export function App() {
|
||||
<SearchFrame presets={presets} selectedFrame={selectedFrame} />
|
||||
<HistoryFrame presets={presets} selectedFrame={selectedFrame} />
|
||||
<SettingsFrame
|
||||
marquee={marquee}
|
||||
selectedFrame={selectedFrame}
|
||||
appTheme={appTheme}
|
||||
backgroundMode={backgroundMode}
|
||||
backgroundOpacity={backgroundOpacity}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class={classPropToString([
|
||||
env.standalone && "pb-6",
|
||||
"flex justify-between gap-3 border-t border-white/10 bg-black/30 p-2 backdrop-blur-sm md:hidden",
|
||||
standalone && "pb-6",
|
||||
"border-lighter flex justify-between gap-3 border-t bg-black/30 p-2 backdrop-blur-sm sm:justify-around md:hidden short:hidden",
|
||||
])}
|
||||
>
|
||||
<StripMobile
|
||||
@@ -259,27 +335,21 @@ export function App() {
|
||||
|
||||
<Show when={!fullscreen()}>
|
||||
<div
|
||||
class="mx-[3px] my-8 hidden w-[6px] cursor-col-resize items-center justify-center rounded-full bg-orange-100 opacity-0 hover:opacity-50 md:block"
|
||||
class="mx-[3px] my-8 hidden w-[6px] cursor-col-resize items-center justify-center rounded-full bg-orange-900 opacity-0 hover:opacity-50 dark:bg-orange-100 md:block short:hidden"
|
||||
onMouseDown={(event) => {
|
||||
resizeInitialRange.set(chartState.range);
|
||||
|
||||
if (resizingBarStart() === undefined) {
|
||||
resizingBarStart.set(event.clientX);
|
||||
resizingBarWidth.set(barWidth());
|
||||
}
|
||||
}}
|
||||
onTouchStart={(event) => {
|
||||
resizeInitialRange.set(chartState.range);
|
||||
|
||||
if (resizingBarStart() === undefined) {
|
||||
resizingBarStart.set(event.touches[0].clientX);
|
||||
resizingBarWidth.set(barWidth());
|
||||
}
|
||||
}}
|
||||
onDblClick={() => {
|
||||
resizeInitialRange.set(chartState.range);
|
||||
barWidth.set(0);
|
||||
setTimeScale(resizeInitialRange());
|
||||
}}
|
||||
/>
|
||||
</Show>
|
||||
@@ -291,8 +361,8 @@ export function App() {
|
||||
presets={presets}
|
||||
qrcode={qrcode}
|
||||
fullscreen={fullscreen}
|
||||
activeResources={activeResources}
|
||||
datasets={datasets}
|
||||
dark={dark}
|
||||
/>
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
Vendored
+1
-1
@@ -1,6 +1,6 @@
|
||||
type FrameName =
|
||||
| "Chart"
|
||||
| "Tree"
|
||||
| "Folders"
|
||||
| "Favorites"
|
||||
| "Search"
|
||||
| "History"
|
||||
|
||||
+31
-3
@@ -1,3 +1,31 @@
|
||||
export const env = {
|
||||
standalone: "standalone" in window.navigator && !!window.navigator.standalone,
|
||||
};
|
||||
export const standalone =
|
||||
"standalone" in window.navigator && !!window.navigator.standalone;
|
||||
|
||||
export const touchScreen =
|
||||
"ontouchstart" in window ||
|
||||
navigator.maxTouchPoints > 0 ||
|
||||
(navigator as any).msMaxTouchPoints > 0;
|
||||
|
||||
export const requestIdleCallbackPossible = "requestIdleCallback" in window;
|
||||
|
||||
console.log(navigator.userAgent);
|
||||
export const macOS = navigator.userAgent.toLowerCase().includes("mac os");
|
||||
|
||||
export const iphone = navigator.userAgent.toLowerCase().includes("iphone");
|
||||
|
||||
export const ipad = navigator.userAgent.toLowerCase().includes("ipad");
|
||||
|
||||
export const chrome = navigator.userAgent.toLowerCase().includes("chrome");
|
||||
|
||||
export const firefox = navigator.userAgent.toLowerCase().includes("firefox");
|
||||
|
||||
export const gecko = navigator.userAgent.toLowerCase().includes("gecko");
|
||||
|
||||
export const safari = navigator.userAgent.toLowerCase().includes("safari");
|
||||
|
||||
export const safariOnly = safari && !chrome;
|
||||
|
||||
export const phone =
|
||||
/Android|webOS|iPhone|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
|
||||
navigator.userAgent,
|
||||
);
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import { createResourceDataset } from "./resource";
|
||||
|
||||
export { averages } from "./consts/averages";
|
||||
|
||||
export function createScaleDatasets<Scale extends ResourceScale>({
|
||||
scale,
|
||||
groupedKeysToURLPath,
|
||||
}: {
|
||||
scale: Scale;
|
||||
groupedKeysToURLPath: GroupedKeysToURLPath[Scale];
|
||||
}) {
|
||||
type Key = keyof typeof groupedKeysToURLPath;
|
||||
type ResourceData = ReturnType<typeof createResourceDataset<Scale>>;
|
||||
|
||||
type ResourceDatasets = Record<Exclude<Key, "ohlc">, ResourceData>;
|
||||
|
||||
const datasets = groupedKeysToURLPath as any as ResourceDatasets;
|
||||
|
||||
for (const key in groupedKeysToURLPath) {
|
||||
if ((key as Key) !== "ohlc") {
|
||||
datasets[key as unknown as Exclude<Key, "ohlc">] = createResourceDataset({
|
||||
scale,
|
||||
path: groupedKeysToURLPath[key as Key] as any,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const price = createResourceDataset<Scale, OHLC>({
|
||||
scale,
|
||||
path: `/${scale}-to-ohlc`,
|
||||
});
|
||||
|
||||
Object.assign(datasets, { price });
|
||||
|
||||
return datasets;
|
||||
}
|
||||
+4
-1
@@ -9,7 +9,10 @@ type AddressCohortKeySplitByLiquidity = `${LiquidityKey}_${AddressCohortKey}`;
|
||||
|
||||
type AnyCohortKey = AgeCohortKey | AddressCohortKey;
|
||||
|
||||
type AnyPossibleCohortKey = AnyCohortKey | AddressCohortKeySplitByLiquidity;
|
||||
type AnyPossibleCohortKey =
|
||||
| AnyCohortKey
|
||||
| AddressCohortKeySplitByLiquidity
|
||||
| LiquidityKey;
|
||||
|
||||
type AverageName = (typeof import("./averages").averages)[number]["key"];
|
||||
|
||||
|
||||
@@ -3,10 +3,8 @@ import { createResourceDataset } from "./resource";
|
||||
export { averages } from "./consts/averages";
|
||||
|
||||
export function createDateDatasets({
|
||||
setActiveResources,
|
||||
groupedKeysToURLPath,
|
||||
}: {
|
||||
setActiveResources: Setter<Set<ResourceDataset<any, any>>>;
|
||||
groupedKeysToURLPath: GroupedKeysToURLPath["date"];
|
||||
}) {
|
||||
type Key = keyof typeof groupedKeysToURLPath;
|
||||
@@ -14,33 +12,25 @@ export function createDateDatasets({
|
||||
|
||||
type ResourceDatasets = Record<Exclude<Key, "ohlc">, ResourceData>;
|
||||
|
||||
for (const _key in groupedKeysToURLPath) {
|
||||
const key = _key as Key;
|
||||
const datasets = groupedKeysToURLPath as any as ResourceDatasets;
|
||||
|
||||
if (key !== "ohlc") {
|
||||
const path = groupedKeysToURLPath[key];
|
||||
|
||||
(groupedKeysToURLPath as any as ResourceDatasets)[key] =
|
||||
createResourceDataset<"date">({
|
||||
scale: "date",
|
||||
path,
|
||||
setActiveResources,
|
||||
});
|
||||
for (const key in groupedKeysToURLPath) {
|
||||
if ((key as Key) !== "ohlc") {
|
||||
datasets[key as Exclude<Key, "ohlc">] = createResourceDataset<"date">({
|
||||
scale: "date",
|
||||
path: groupedKeysToURLPath[key as Key],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const resourceDatasets = groupedKeysToURLPath as any as ResourceDatasets;
|
||||
|
||||
const price = createResourceDataset<"date", OHLC>({
|
||||
scale: "date",
|
||||
path: "/date-to-ohlc",
|
||||
setActiveResources,
|
||||
});
|
||||
|
||||
const datasets = {
|
||||
price,
|
||||
...resourceDatasets,
|
||||
};
|
||||
Object.assign(datasets, { price });
|
||||
|
||||
return datasets;
|
||||
return datasets as ResourceDatasets & {
|
||||
price: ResourceDataset<"date", OHLC>;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import { createResourceDataset } from "./resource";
|
||||
|
||||
export function createHeightDatasets({
|
||||
setActiveResources,
|
||||
groupedKeysToURLPath,
|
||||
}: {
|
||||
setActiveResources: Setter<Set<ResourceDataset<any, any>>>;
|
||||
groupedKeysToURLPath: GroupedKeysToURLPath["height"];
|
||||
}) {
|
||||
type Key = keyof typeof groupedKeysToURLPath;
|
||||
@@ -12,31 +10,25 @@ export function createHeightDatasets({
|
||||
|
||||
type ResourceDatasets = Record<Exclude<Key, "ohlc">, ResourceData>;
|
||||
|
||||
for (const _key in groupedKeysToURLPath) {
|
||||
const key = _key as Key;
|
||||
const datasets = groupedKeysToURLPath as any as ResourceDatasets;
|
||||
|
||||
if (key !== "ohlc") {
|
||||
const path = groupedKeysToURLPath[key];
|
||||
|
||||
(groupedKeysToURLPath as any as ResourceDatasets)[key] =
|
||||
createResourceDataset<"height">({
|
||||
scale: "height",
|
||||
path,
|
||||
setActiveResources,
|
||||
});
|
||||
for (const key in groupedKeysToURLPath) {
|
||||
if ((key as Key) !== "ohlc") {
|
||||
datasets[key as Exclude<Key, "ohlc">] = createResourceDataset<"height">({
|
||||
scale: "height",
|
||||
path: groupedKeysToURLPath[key as Key],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const resourceDatasets = groupedKeysToURLPath as any as ResourceDatasets;
|
||||
|
||||
const price = createResourceDataset<"height", OHLC>({
|
||||
scale: "height",
|
||||
path: "/height-to-ohlc",
|
||||
setActiveResources,
|
||||
});
|
||||
|
||||
return {
|
||||
...resourceDatasets,
|
||||
price,
|
||||
Object.assign(datasets, { price });
|
||||
|
||||
return datasets as ResourceDatasets & {
|
||||
price: ResourceDataset<"height", OHLC>;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -7,18 +7,12 @@ export const scales = ["date" as const, "height" as const];
|
||||
|
||||
export const HEIGHT_CHUNK_SIZE = 10_000;
|
||||
|
||||
export function createDatasets({
|
||||
setActiveResources,
|
||||
}: {
|
||||
setActiveResources: Setter<Set<ResourceDataset<any, any>>>;
|
||||
}) {
|
||||
export function createDatasets() {
|
||||
const date = createDateDatasets({
|
||||
setActiveResources,
|
||||
groupedKeysToURLPath: groupedKeysToURLPath.date,
|
||||
});
|
||||
|
||||
const height = createHeightDatasets({
|
||||
setActiveResources,
|
||||
groupedKeysToURLPath: groupedKeysToURLPath.height,
|
||||
});
|
||||
|
||||
|
||||
@@ -1,10 +1,4 @@
|
||||
import { createLazyMemo } from "@solid-primitives/memo";
|
||||
|
||||
import {
|
||||
ONE_DAY_IN_MS,
|
||||
ONE_HOUR_IN_MS,
|
||||
ONE_MINUTE_IN_MS,
|
||||
} from "/src/scripts/utils/time";
|
||||
import { ONE_HOUR_IN_MS, ONE_MINUTE_IN_MS } from "/src/scripts/utils/time";
|
||||
import { createRWS } from "/src/solid/rws";
|
||||
|
||||
import { HEIGHT_CHUNK_SIZE } from ".";
|
||||
@@ -12,22 +6,7 @@ import { HEIGHT_CHUNK_SIZE } from ".";
|
||||
export function createResourceDataset<
|
||||
Scale extends ResourceScale,
|
||||
Type extends OHLC | number = number,
|
||||
>({
|
||||
scale,
|
||||
path,
|
||||
setActiveResources,
|
||||
}: {
|
||||
scale: Scale;
|
||||
path: string;
|
||||
setActiveResources: Setter<Set<ResourceDataset<any, any>>>;
|
||||
}) {
|
||||
const baseURL = `${
|
||||
// location.hostname === "localhost"
|
||||
// ? "http://localhost:3110"
|
||||
// : "https://api.satonomics.xyz"
|
||||
"https://api.satonomics.xyz"
|
||||
}${path}`;
|
||||
|
||||
>({ scale, path }: { scale: Scale; path: string }) {
|
||||
type Dataset = Scale extends "date"
|
||||
? FetchedDateDataset<Type>
|
||||
: FetchedHeightDataset<Type>;
|
||||
@@ -36,6 +15,13 @@ export function createResourceDataset<
|
||||
Type extends number ? SingleValueData : CandlestickData
|
||||
>;
|
||||
|
||||
const baseURL = `${
|
||||
// location.hostname === "localhost"
|
||||
// ? "http://localhost:3110"
|
||||
// : "https://api.satonomics.xyz"
|
||||
"https://api.satonomics.xyz"
|
||||
}${path}`;
|
||||
|
||||
const fetchedJSONs = new Array(
|
||||
(new Date().getFullYear() - new Date("2009-01-01").getFullYear() + 2) *
|
||||
(scale === "date" ? 1 : 6),
|
||||
@@ -51,28 +37,31 @@ export function createResourceDataset<
|
||||
vec: createMemo(() => {
|
||||
const map = json()?.dataset.map || null;
|
||||
|
||||
const chunkId = json()?.chunk.id!;
|
||||
|
||||
if (!map) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const chunkId = json()?.chunk.id!;
|
||||
|
||||
if (Array.isArray(map)) {
|
||||
return map.map(
|
||||
(value, index) =>
|
||||
({
|
||||
number: chunkId + index,
|
||||
time: (chunkId + index) as Time,
|
||||
...(typeof value !== "number" && value !== null
|
||||
? { ...(value as OHLC), value: value.close }
|
||||
: { value: value === null ? NaN : (value as number) }),
|
||||
}) as any as Value,
|
||||
);
|
||||
const values = new Array(map.length);
|
||||
|
||||
for (let i = 0; i < map.length; i++) {
|
||||
const value = map[i];
|
||||
|
||||
values[i] = {
|
||||
time: (chunkId + i) as Time,
|
||||
...(typeof value !== "number" && value !== null
|
||||
? { ...(value as OHLC), value: value.close }
|
||||
: { value: value === null ? NaN : (value as number) }),
|
||||
} as any as Value;
|
||||
}
|
||||
|
||||
return values;
|
||||
} else {
|
||||
return Object.entries(map).map(
|
||||
([date, value]) =>
|
||||
({
|
||||
number: new Date(date).valueOf() / ONE_DAY_IN_MS,
|
||||
time: date,
|
||||
...(typeof value !== "number" && value !== null
|
||||
? { ...(value as OHLC), value: value.close }
|
||||
@@ -85,8 +74,7 @@ export function createResourceDataset<
|
||||
}) as FetchedResult<Scale, Type>[];
|
||||
|
||||
const _fetch = async (id: number) => {
|
||||
const index =
|
||||
scale === "date" ? id - 2009 : Math.floor(id / HEIGHT_CHUNK_SIZE);
|
||||
const index = chunkIdToIndex(scale, id);
|
||||
|
||||
if (
|
||||
index < 0 ||
|
||||
@@ -99,10 +87,18 @@ export function createResourceDataset<
|
||||
|
||||
const fetched = fetchedJSONs.at(index);
|
||||
|
||||
if (scale === "height" && index > 0) {
|
||||
const length = fetchedJSONs.at(index - 1)?.vec()?.length;
|
||||
|
||||
if (length !== undefined && length < HEIGHT_CHUNK_SIZE) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!fetched || fetched.loading) {
|
||||
return;
|
||||
} else if (fetched.at) {
|
||||
const diff = new Date().valueOf() - fetched.at.valueOf();
|
||||
const diff = new Date().getTime() - fetched.at.getTime();
|
||||
|
||||
if (
|
||||
diff < ONE_MINUTE_IN_MS ||
|
||||
@@ -137,6 +133,11 @@ export function createResourceDataset<
|
||||
} catch {}
|
||||
}
|
||||
|
||||
if (!navigator.onLine) {
|
||||
fetched.loading = false;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const fetchedResponse = await fetch(urlWithQuery);
|
||||
|
||||
@@ -163,7 +164,7 @@ export function createResourceDataset<
|
||||
return;
|
||||
}
|
||||
|
||||
if (previousLength && previousLength <= newLength) {
|
||||
if (previousLength && previousLength === newLength) {
|
||||
const previousLastValue = Object.values(previousMap || []).at(-1);
|
||||
const newLastValue = Object.values(newMap).at(-1);
|
||||
|
||||
@@ -210,20 +211,6 @@ export function createResourceDataset<
|
||||
url: baseURL,
|
||||
fetch: _fetch,
|
||||
fetchedJSONs,
|
||||
values: createLazyMemo(() => {
|
||||
setActiveResources((resources) => resources.add(resource));
|
||||
|
||||
onCleanup(() =>
|
||||
setActiveResources((resources) => {
|
||||
resources.delete(resource);
|
||||
return resources;
|
||||
}),
|
||||
);
|
||||
|
||||
const flat = fetchedJSONs.flatMap((fetched) => fetched.vec() || []);
|
||||
|
||||
return flat;
|
||||
}),
|
||||
drop() {
|
||||
fetchedJSONs.forEach((fetched) => {
|
||||
fetched.at = null;
|
||||
@@ -245,3 +232,7 @@ async function convertResponseToJSON<
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export function chunkIdToIndex(scale: ResourceScale, id: number) {
|
||||
return scale === "date" ? id - 2009 : Math.floor(id / HEIGHT_CHUNK_SIZE);
|
||||
}
|
||||
|
||||
Vendored
+3
-10
@@ -6,15 +6,7 @@ type AnyDatasets = DateDatasets | HeightDatasets;
|
||||
|
||||
type ResourceScale = (typeof import("./index").scales)[index];
|
||||
|
||||
type DatasetValue<T> = T & Numbered & Valued;
|
||||
|
||||
interface Dataset<
|
||||
Scale extends ResourceScale,
|
||||
Value extends SingleValueData | CandlestickData = SingleValueData,
|
||||
> {
|
||||
scale: Scale;
|
||||
values: Accessor<DatasetValue<Value>[]>;
|
||||
}
|
||||
type DatasetValue<T> = T & Valued;
|
||||
|
||||
interface ResourceDataset<
|
||||
Scale extends ResourceScale,
|
||||
@@ -27,7 +19,8 @@ interface ResourceDataset<
|
||||
Value extends SingleValueData | CandlestickData = Type extends number
|
||||
? SingleValueData
|
||||
: CandlestickData,
|
||||
> extends Dataset<Scale, Value> {
|
||||
> {
|
||||
scale: Scale;
|
||||
url: string;
|
||||
fetch: (id: number) => void;
|
||||
fetchedJSONs: FetchedResult<Scale, Type>[];
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
import { colors } from "/src/scripts/utils/colors";
|
||||
|
||||
import { defaultSeriesOptions } from "./options";
|
||||
|
||||
const DEFAULT_BASELINE_TOP_COLOR = colors.profit;
|
||||
const DEFAULT_BASELINE_BOTTOM_COLOR = colors.loss;
|
||||
|
||||
export const DEFAULT_BASELINE_COLORS = [
|
||||
DEFAULT_BASELINE_TOP_COLOR,
|
||||
DEFAULT_BASELINE_BOTTOM_COLOR,
|
||||
];
|
||||
|
||||
const transparent = `transparent`;
|
||||
|
||||
export const createBaseLineSeries = ({
|
||||
chart,
|
||||
dark,
|
||||
color,
|
||||
topColor,
|
||||
bottomColor,
|
||||
options,
|
||||
}: {
|
||||
chart: IChartApi;
|
||||
dark: Accessor<boolean>;
|
||||
color?: Color;
|
||||
topColor?: Color;
|
||||
bottomColor?: Color;
|
||||
options?: DeepPartialBaselineOptions & {
|
||||
base?: number;
|
||||
};
|
||||
}) => {
|
||||
const topLineColor = topColor || color || DEFAULT_BASELINE_TOP_COLOR;
|
||||
|
||||
const bottomLineColor = bottomColor || color || DEFAULT_BASELINE_BOTTOM_COLOR;
|
||||
|
||||
const seriesOptions: DeepPartialBaselineOptions = {
|
||||
priceScaleId: "right",
|
||||
...defaultSeriesOptions,
|
||||
// lineWidth: 1,
|
||||
...options,
|
||||
...(options?.base
|
||||
? { baseValue: { type: "price", price: options?.base } }
|
||||
: {}),
|
||||
topFillColor1: transparent,
|
||||
topFillColor2: transparent,
|
||||
bottomFillColor1: transparent,
|
||||
bottomFillColor2: transparent,
|
||||
};
|
||||
|
||||
const series = chart.addBaselineSeries(seriesOptions);
|
||||
|
||||
createEffect(() => {
|
||||
series.applyOptions({
|
||||
topLineColor: topLineColor(dark),
|
||||
bottomLineColor: bottomLineColor(dark),
|
||||
});
|
||||
});
|
||||
|
||||
return series;
|
||||
};
|
||||
@@ -0,0 +1,43 @@
|
||||
import { colors } from "/src/scripts/utils/colors";
|
||||
|
||||
export const createCandlesticksSeries = ({
|
||||
chart,
|
||||
dark,
|
||||
options = {},
|
||||
}: {
|
||||
chart: IChartApi;
|
||||
dark: Accessor<boolean>;
|
||||
options?: PriceSeriesOptions;
|
||||
}): [ISeriesApi<"Candlestick">, Color[]] => {
|
||||
const { inverseColors } = options;
|
||||
|
||||
const candlestickSeries = chart.addCandlestickSeries({
|
||||
baseLineVisible: false,
|
||||
borderVisible: false,
|
||||
priceLineVisible: false,
|
||||
baseLineColor: "",
|
||||
borderColor: "",
|
||||
borderDownColor: "",
|
||||
borderUpColor: "",
|
||||
...options.seriesOptions,
|
||||
});
|
||||
|
||||
const _upColor = inverseColors ? colors.loss : colors.profit;
|
||||
|
||||
const _downColor = inverseColors ? colors.profit : colors.loss;
|
||||
|
||||
createEffect(() => {
|
||||
const upColor = _upColor(dark);
|
||||
|
||||
const downColor = _downColor(dark);
|
||||
|
||||
candlestickSeries.applyOptions({
|
||||
upColor,
|
||||
wickUpColor: upColor,
|
||||
downColor,
|
||||
wickDownColor: downColor,
|
||||
});
|
||||
});
|
||||
|
||||
return [candlestickSeries, [_upColor, _downColor]];
|
||||
};
|
||||
@@ -1,11 +0,0 @@
|
||||
import { chartState } from "./state";
|
||||
|
||||
export function cleanChart() {
|
||||
console.log("chart: clean");
|
||||
|
||||
try {
|
||||
chartState.chart?.remove();
|
||||
} catch {}
|
||||
|
||||
chartState.chart = null;
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
import {
|
||||
createChart as createClassicChart,
|
||||
createChartEx as createCustomChart,
|
||||
CrosshairMode,
|
||||
} from "lightweight-charts";
|
||||
|
||||
import { colors } from "../../utils/colors";
|
||||
import { priceToUSLocale } from "../../utils/locale";
|
||||
import { cleanChart } from "./clean";
|
||||
import { HorzScaleBehaviorHeight } from "./horzScaleBehavior";
|
||||
import { chartState } from "./state";
|
||||
|
||||
export function createChart(scale: ResourceScale) {
|
||||
cleanChart();
|
||||
|
||||
console.log(`chart: create (scale: ${scale})`);
|
||||
|
||||
const { white } = colors;
|
||||
|
||||
const options: DeepPartialChartOptions = {
|
||||
autoSize: true,
|
||||
layout: {
|
||||
fontFamily: "Lexend",
|
||||
background: { color: "transparent" },
|
||||
fontSize: 14,
|
||||
textColor: white,
|
||||
},
|
||||
grid: {
|
||||
vertLines: { visible: false },
|
||||
horzLines: { visible: false },
|
||||
},
|
||||
leftPriceScale: {
|
||||
// borderColor: white,
|
||||
},
|
||||
rightPriceScale: {
|
||||
// borderColor: white,
|
||||
},
|
||||
timeScale: {
|
||||
minBarSpacing: scale === "date" ? 0.05 : 0.005,
|
||||
shiftVisibleRangeOnNewBar: false,
|
||||
allowShiftVisibleRangeOnWhitespaceReplacement: false,
|
||||
},
|
||||
crosshair: {
|
||||
mode: CrosshairMode.Normal,
|
||||
horzLine: {
|
||||
color: white,
|
||||
labelBackgroundColor: white,
|
||||
},
|
||||
vertLine: {
|
||||
color: white,
|
||||
labelBackgroundColor: white,
|
||||
},
|
||||
},
|
||||
localization: {
|
||||
priceFormatter: priceToUSLocale,
|
||||
locale: "en-us",
|
||||
},
|
||||
};
|
||||
|
||||
if (scale === "date") {
|
||||
chartState.chart = createClassicChart("chart", options);
|
||||
} else {
|
||||
const horzScaleBehavior = new HorzScaleBehaviorHeight();
|
||||
|
||||
// @ts-ignore
|
||||
chartState.chart = createCustomChart("chart", horzScaleBehavior, options);
|
||||
}
|
||||
}
|
||||
@@ -1,123 +0,0 @@
|
||||
import { colors } from "/src/scripts/utils/colors";
|
||||
import { priceToUSLocale } from "/src/scripts/utils/locale";
|
||||
import { ONE_DAY_IN_MS } from "/src/scripts/utils/time";
|
||||
|
||||
import { chartState } from "./state";
|
||||
import { GENESIS_DAY } from "./whitespace";
|
||||
|
||||
export const setMinMaxMarkers = ({
|
||||
scale,
|
||||
candlesticks,
|
||||
range,
|
||||
lowerOpacity,
|
||||
}: {
|
||||
scale: ResourceScale;
|
||||
candlesticks: DatasetValue<CandlestickData | SingleValueData>[];
|
||||
range: TimeRange;
|
||||
lowerOpacity: boolean;
|
||||
}) => {
|
||||
const first = candlesticks.at(0);
|
||||
|
||||
if (!first) return;
|
||||
|
||||
const offset =
|
||||
scale === "date"
|
||||
? first.number - new Date(GENESIS_DAY).valueOf() / ONE_DAY_IN_MS
|
||||
: 0;
|
||||
|
||||
const slicedDataList = range
|
||||
? candlesticks.slice(
|
||||
Math.ceil(range.from - offset < 0 ? 0 : range.from - offset),
|
||||
Math.floor(range.to - offset) + 1,
|
||||
)
|
||||
: [];
|
||||
|
||||
const series = chartState.priceSeries;
|
||||
|
||||
if (!series) return;
|
||||
|
||||
if (slicedDataList.length) {
|
||||
const markers: (SeriesMarker<Time> & Numbered)[] = [];
|
||||
|
||||
const seriesIsCandlestick = series.seriesType() === "Candlestick";
|
||||
|
||||
[
|
||||
{
|
||||
mathFunction: "min" as const,
|
||||
placementAttribute: seriesIsCandlestick
|
||||
? ("low" as const)
|
||||
: ("close" as const),
|
||||
// valueAttribute: 'low' as const,
|
||||
markerOptions: {
|
||||
position: "belowBar" as const,
|
||||
shape: "arrowUp" as const,
|
||||
},
|
||||
},
|
||||
{
|
||||
mathFunction: "max" as const,
|
||||
placementAttribute: seriesIsCandlestick
|
||||
? ("high" as const)
|
||||
: ("close" as const),
|
||||
// valueAttribute: 'high' as const,
|
||||
markerOptions: {
|
||||
position: "aboveBar" as const,
|
||||
shape: "arrowDown" as const,
|
||||
},
|
||||
},
|
||||
].map(
|
||||
({
|
||||
mathFunction,
|
||||
placementAttribute,
|
||||
// valueAttribute,
|
||||
markerOptions,
|
||||
}) => {
|
||||
const value = Math[mathFunction](
|
||||
// ...slicedDataList.map((data) => data[valueAttribute] || 0),
|
||||
...slicedDataList.map(
|
||||
(data) =>
|
||||
(placementAttribute in data
|
||||
? data[placementAttribute]
|
||||
: data.value) || 0,
|
||||
),
|
||||
);
|
||||
|
||||
const placement = Math[mathFunction](
|
||||
...slicedDataList.map(
|
||||
(data) =>
|
||||
(placementAttribute in data
|
||||
? data[placementAttribute]
|
||||
: data.value) || 0,
|
||||
),
|
||||
);
|
||||
|
||||
const candle = slicedDataList.find(
|
||||
(data) =>
|
||||
(placementAttribute in data
|
||||
? data[placementAttribute]
|
||||
: data.value) === placement,
|
||||
);
|
||||
|
||||
return (
|
||||
candle &&
|
||||
markers.push({
|
||||
...markerOptions,
|
||||
// date: candle.date,
|
||||
number: candle.number,
|
||||
time: candle.time,
|
||||
color: lowerOpacity ? colors.darkWhite : colors.white,
|
||||
size: 0,
|
||||
text: priceToUSLocale(value),
|
||||
})
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
series.setMarkers(sortWhitespaceDataArray(markers));
|
||||
}
|
||||
};
|
||||
|
||||
function sortWhitespaceDataArray<T extends WhitespaceData & Numbered>(
|
||||
array: T[],
|
||||
) {
|
||||
return array.sort(({ number: a }, { number: b }) => a - b);
|
||||
}
|
||||
@@ -1,176 +0,0 @@
|
||||
import { createRWS } from "/src/solid/rws";
|
||||
|
||||
import { colors } from "../../utils/colors";
|
||||
import { getNumberOfDaysBetweenTwoDates } from "../../utils/date";
|
||||
import { debounce } from "../../utils/debounce";
|
||||
import { webSockets } from "../../ws";
|
||||
import { createCandlesticksSeries } from "../series/creators/candlesticks";
|
||||
import { createSeriesLegend } from "../series/creators/legend";
|
||||
import { createLineSeries } from "../series/creators/line";
|
||||
import { setMinMaxMarkers } from "./markers";
|
||||
import { chartState } from "./state";
|
||||
import { initTimeScale } from "./time";
|
||||
|
||||
export const PRICE_SCALE_MOMENTUM_ID = "momentum";
|
||||
|
||||
export const applyPriceSeries = <
|
||||
Scale extends ResourceScale,
|
||||
T extends SingleValueData,
|
||||
>({
|
||||
chart,
|
||||
datasets,
|
||||
preset,
|
||||
dataset: _dataset,
|
||||
options,
|
||||
activeResources,
|
||||
}: {
|
||||
chart: IChartApi;
|
||||
datasets: Datasets;
|
||||
preset: Preset;
|
||||
activeResources: Accessor<Set<ResourceDataset<any, any>>>;
|
||||
dataset?: Dataset<Scale, T>;
|
||||
options?: PriceSeriesOptions;
|
||||
}) => {
|
||||
const id = options?.id || "price";
|
||||
const title = options?.title || "Price";
|
||||
|
||||
const dataset = createMemo(() => _dataset || datasets[preset.scale].price);
|
||||
|
||||
const url = "url" in dataset() ? (dataset() as any).url : undefined;
|
||||
|
||||
const priceScaleOptions: DeepPartial<PriceScaleOptions> = {
|
||||
...(options?.halved
|
||||
? {
|
||||
scaleMargins: {
|
||||
top: 0.05,
|
||||
bottom: 0.55,
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
...(options?.id || options?.title
|
||||
? {}
|
||||
: {
|
||||
mode: 1,
|
||||
// mode: PriceScaleMode.Logarithmic,
|
||||
}),
|
||||
...options?.priceScaleOptions,
|
||||
};
|
||||
|
||||
const seriesType = createRWS(
|
||||
checkIfUpClose(chart, chartState.range) || "Candlestick",
|
||||
);
|
||||
|
||||
const debouncedCallback = debounce((range: TimeRange | null) => {
|
||||
try {
|
||||
seriesType.set((previous) => checkIfUpClose(chart, range) || previous);
|
||||
} catch {}
|
||||
}, 50);
|
||||
|
||||
chart?.timeScale().subscribeVisibleTimeRangeChange(debouncedCallback);
|
||||
|
||||
onCleanup(
|
||||
() =>
|
||||
chart === chartState.chart &&
|
||||
chartState.chart
|
||||
?.timeScale()
|
||||
.unsubscribeVisibleTimeRangeChange(debouncedCallback),
|
||||
);
|
||||
|
||||
const lowerOpacity = options?.lowerOpacity || options?.halved || false;
|
||||
|
||||
if (options?.halved) {
|
||||
options.seriesOptions = {
|
||||
...options.seriesOptions,
|
||||
priceScaleId: "left",
|
||||
};
|
||||
}
|
||||
|
||||
const [ohlcSeries, ohlcColors] = createCandlesticksSeries(chart, {
|
||||
...options,
|
||||
lowerOpacity,
|
||||
});
|
||||
|
||||
const ohlcLegend = createSeriesLegend({
|
||||
id,
|
||||
presetId: preset.id,
|
||||
title,
|
||||
color: () => ohlcColors,
|
||||
series: ohlcSeries,
|
||||
disabled: () => seriesType() !== "Candlestick",
|
||||
url,
|
||||
});
|
||||
|
||||
ohlcSeries.priceScale().applyOptions(priceScaleOptions);
|
||||
|
||||
// ---
|
||||
|
||||
const lineColor = lowerOpacity ? colors.darkWhite : colors.white;
|
||||
|
||||
const lineSeries = createLineSeries(chart, {
|
||||
color: lineColor,
|
||||
...options?.seriesOptions,
|
||||
});
|
||||
|
||||
const lineLegend = createSeriesLegend({
|
||||
id,
|
||||
presetId: preset.id,
|
||||
title,
|
||||
color: () => lineColor,
|
||||
series: lineSeries,
|
||||
disabled: () => seriesType() !== "Line",
|
||||
visible: ohlcLegend.visible,
|
||||
url,
|
||||
});
|
||||
|
||||
lineSeries.priceScale().applyOptions(priceScaleOptions);
|
||||
|
||||
// ---
|
||||
|
||||
// setMinMaxMarkers({
|
||||
// scale: preset.scale,
|
||||
// candlesticks:
|
||||
// dataset?.values() || datasets[preset.scale].price.values() || ([] as any),
|
||||
// range: chartState.range,
|
||||
// lowerOpacity,
|
||||
// });
|
||||
|
||||
initTimeScale({
|
||||
activeResources,
|
||||
});
|
||||
|
||||
createEffect(() => {
|
||||
const d = dataset();
|
||||
lineSeries.setData(d.values());
|
||||
ohlcSeries.setData(d.values());
|
||||
});
|
||||
|
||||
createEffect(() => {
|
||||
if (preset.scale === "date") {
|
||||
const latest = webSockets.liveKrakenCandle.latest();
|
||||
|
||||
if (latest) {
|
||||
ohlcSeries.update(latest);
|
||||
lineSeries.update(latest);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return { ohlcLegend, lineLegend };
|
||||
};
|
||||
|
||||
function checkIfUpClose(chart: IChartApi, range?: TimeRange | null) {
|
||||
if (!range) return undefined;
|
||||
|
||||
const from = new Date(range.from);
|
||||
const to = new Date(range.to);
|
||||
|
||||
const width = chart.timeScale().width();
|
||||
|
||||
const difference = getNumberOfDaysBetweenTwoDates(from, to);
|
||||
|
||||
return width / difference >= 2.05
|
||||
? "Candlestick"
|
||||
: width / difference <= 1.95
|
||||
? "Line"
|
||||
: undefined;
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
import { createChart } from "./create";
|
||||
import { chartState } from "./state";
|
||||
import { setWhitespace } from "./whitespace";
|
||||
|
||||
export function renderChart({
|
||||
datasets,
|
||||
legendSetter,
|
||||
preset,
|
||||
activeResources,
|
||||
}: {
|
||||
datasets: Datasets;
|
||||
legendSetter: Setter<PresetLegend>;
|
||||
preset: Preset;
|
||||
activeResources: Accessor<Set<ResourceDataset<any, any>>>;
|
||||
}) {
|
||||
const scale = preset.scale;
|
||||
|
||||
createChart(scale);
|
||||
|
||||
const chart = chartState.chart;
|
||||
|
||||
if (!chart) return;
|
||||
|
||||
try {
|
||||
setWhitespace(chart, scale);
|
||||
|
||||
console.log(`preset: ${preset.id}`);
|
||||
|
||||
const legend = preset.applyPreset({
|
||||
chart,
|
||||
datasets,
|
||||
preset,
|
||||
activeResources,
|
||||
});
|
||||
|
||||
legendSetter(legend);
|
||||
} catch (error) {
|
||||
console.error("chart: render: failed", error);
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
import { getInitialRange } from "./time";
|
||||
|
||||
export const LOCAL_STORAGE_RANGE_KEY = "chart-range";
|
||||
export const URL_PARAMS_RANGE_FROM_KEY = "from";
|
||||
export const URL_PARAMS_RANGE_TO_KEY = "to";
|
||||
|
||||
export const chartState = {
|
||||
chart: null as IChartApi | null,
|
||||
range: getInitialRange(),
|
||||
};
|
||||
@@ -1,108 +0,0 @@
|
||||
import { HEIGHT_CHUNK_SIZE } from "../../datasets";
|
||||
import { debounce } from "../../utils/debounce";
|
||||
import { writeURLParam } from "../../utils/urlParams";
|
||||
import { setMinMaxMarkers } from "./markers";
|
||||
import {
|
||||
chartState,
|
||||
LOCAL_STORAGE_RANGE_KEY,
|
||||
URL_PARAMS_RANGE_FROM_KEY,
|
||||
URL_PARAMS_RANGE_TO_KEY,
|
||||
} from "./state";
|
||||
|
||||
const debouncedUpdateURLParams = debounce((range: TimeRange | null) => {
|
||||
if (!range) return;
|
||||
|
||||
writeURLParam(URL_PARAMS_RANGE_FROM_KEY, String(range.from));
|
||||
|
||||
writeURLParam(URL_PARAMS_RANGE_TO_KEY, String(range.to));
|
||||
|
||||
localStorage.setItem(LOCAL_STORAGE_RANGE_KEY, JSON.stringify(range));
|
||||
}, 500);
|
||||
|
||||
export function initTimeScale({
|
||||
activeResources,
|
||||
}: {
|
||||
activeResources: Accessor<Set<ResourceDataset<any, any>>>;
|
||||
}) {
|
||||
setTimeScale(chartState.range);
|
||||
|
||||
const debouncedFetch = debounce((range: TimeRange | null) => {
|
||||
if (!range) return;
|
||||
|
||||
let ids: number[] = [];
|
||||
|
||||
if (typeof range.from === "string" && typeof range.to === "string") {
|
||||
const from = new Date(range.from).getUTCFullYear();
|
||||
const to = new Date(range.to).getUTCFullYear();
|
||||
|
||||
ids = Array.from({ length: to - from + 1 }, (_, i) => i + from);
|
||||
} else {
|
||||
const from = Math.floor(Number(range.from) / HEIGHT_CHUNK_SIZE);
|
||||
const to = Math.floor(Number(range.to) / HEIGHT_CHUNK_SIZE);
|
||||
|
||||
const length = to - from + 1;
|
||||
|
||||
ids = Array.from({ length }, (_, i) => (from + i) * HEIGHT_CHUNK_SIZE);
|
||||
}
|
||||
|
||||
ids.forEach((id) => {
|
||||
activeResources().forEach((resource) => resource.fetch(id));
|
||||
});
|
||||
}, 100);
|
||||
|
||||
debouncedFetch(chartState.range);
|
||||
|
||||
let timeout = setTimeout(() => {
|
||||
chartState.chart?.timeScale().subscribeVisibleTimeRangeChange((range) => {
|
||||
debouncedFetch(range);
|
||||
|
||||
debouncedUpdateURLParams(range);
|
||||
|
||||
range = range || chartState.range;
|
||||
|
||||
chartState.range = range;
|
||||
});
|
||||
}, 50);
|
||||
onCleanup(() => clearTimeout(timeout));
|
||||
}
|
||||
|
||||
export function getInitialRange(): TimeRange {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
|
||||
const urlFrom = urlParams.get(URL_PARAMS_RANGE_FROM_KEY);
|
||||
const urlTo = urlParams.get(URL_PARAMS_RANGE_TO_KEY);
|
||||
|
||||
if (urlFrom && urlTo) {
|
||||
return {
|
||||
from: urlFrom,
|
||||
to: urlTo,
|
||||
} satisfies TimeRange;
|
||||
}
|
||||
|
||||
const savedTimeRange = JSON.parse(
|
||||
localStorage.getItem(LOCAL_STORAGE_RANGE_KEY) || "null",
|
||||
) as TimeRange | null;
|
||||
|
||||
if (savedTimeRange) {
|
||||
return savedTimeRange;
|
||||
}
|
||||
|
||||
const defaultTo = new Date();
|
||||
const defaultFrom = new Date();
|
||||
defaultFrom.setDate(defaultFrom.getUTCDate() - 6 * 30);
|
||||
|
||||
const defaultTimeRange = {
|
||||
from: defaultFrom.toJSON().split("T")[0],
|
||||
to: defaultTo.toJSON().split("T")[0],
|
||||
} satisfies TimeRange;
|
||||
|
||||
return defaultTimeRange;
|
||||
}
|
||||
|
||||
export function setTimeScale(range: TimeRange | null) {
|
||||
if (range) {
|
||||
setTimeout(() => {
|
||||
chartState.chart?.timeScale().setVisibleRange(range);
|
||||
}, 1);
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
interface PriceSeriesOptions {
|
||||
halved?: boolean;
|
||||
title?: string;
|
||||
id?: string;
|
||||
lowerOpacity?: boolean;
|
||||
inverseColors?: boolean;
|
||||
seriesOptions?: DeepPartial<SeriesOptionsCommon>;
|
||||
priceScaleOptions?: DeepPartial<PriceScaleOptions>;
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
import { dateToString, getNumberOfDaysBetweenTwoDates } from "../../utils/date";
|
||||
import { ONE_DAY_IN_MS } from "../../utils/time";
|
||||
import { createLineSeries } from "../series/creators/line";
|
||||
|
||||
export const DAY_BEFORE_GENESIS_DAY = "2009-01-02";
|
||||
export const GENESIS_DAY = "2009-01-03";
|
||||
// export const DAY_BEFORE_WHITEPAPER_DAY = "2008-10-30";
|
||||
// export const WHITEPAPER_DAY = "2008-10-31";
|
||||
|
||||
const whitespaceStartDate = "1970-01-01";
|
||||
const whitespaceEndDate = "2100-01-01";
|
||||
const whitespaceDateDataset: (SingleValueData & Numbered)[] = new Array(
|
||||
getNumberOfDaysBetweenTwoDates(
|
||||
new Date(whitespaceStartDate),
|
||||
new Date(whitespaceEndDate),
|
||||
),
|
||||
)
|
||||
.fill(0)
|
||||
.map((_, index) => {
|
||||
const date = new Date(whitespaceStartDate);
|
||||
date.setUTCDate(date.getUTCDay() + index);
|
||||
|
||||
return {
|
||||
number: date.valueOf() / ONE_DAY_IN_MS,
|
||||
time: dateToString(date),
|
||||
value: NaN,
|
||||
};
|
||||
});
|
||||
|
||||
const whitespaceHeightDataset: (WhitespaceData & Numbered)[] = new Array(
|
||||
840_000,
|
||||
)
|
||||
.fill(0)
|
||||
.map(
|
||||
(_, index) =>
|
||||
({
|
||||
time: index,
|
||||
number: index,
|
||||
}) as any,
|
||||
);
|
||||
|
||||
export function setWhitespace(chart: IChartApi, scale: ResourceScale) {
|
||||
const whitespaceSeries = createLineSeries(chart);
|
||||
|
||||
if (scale === "date") {
|
||||
whitespaceSeries.setData(whitespaceDateDataset);
|
||||
} else {
|
||||
whitespaceSeries.setData(whitespaceHeightDataset);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
import {
|
||||
createChart as createClassicChart,
|
||||
createChartEx as createCustomChart,
|
||||
CrosshairMode,
|
||||
} from "lightweight-charts";
|
||||
|
||||
import { colors } from "../utils/colors";
|
||||
import { valueToString } from "../utils/locale";
|
||||
import { HorzScaleBehaviorHeight } from "./horzScaleBehavior";
|
||||
|
||||
export function createChart(
|
||||
scale: ResourceScale,
|
||||
element: HTMLElement,
|
||||
{
|
||||
dark,
|
||||
priceScaleOptions,
|
||||
}: {
|
||||
dark: Accessor<boolean>;
|
||||
priceScaleOptions: DeepPartialPriceScaleOptions;
|
||||
},
|
||||
) {
|
||||
console.log(`chart: create (scale: ${scale})`);
|
||||
|
||||
const options: DeepPartialChartOptions = {
|
||||
autoSize: true,
|
||||
layout: {
|
||||
fontFamily: "Lexend",
|
||||
background: { color: "transparent" },
|
||||
fontSize: 14,
|
||||
},
|
||||
grid: {
|
||||
vertLines: { visible: false },
|
||||
horzLines: { visible: false },
|
||||
},
|
||||
timeScale: {
|
||||
minBarSpacing: 0.05,
|
||||
shiftVisibleRangeOnNewBar: false,
|
||||
allowShiftVisibleRangeOnWhitespaceReplacement: false,
|
||||
},
|
||||
handleScale: {
|
||||
axisDoubleClickReset: {
|
||||
time: false,
|
||||
},
|
||||
},
|
||||
crosshair: {
|
||||
mode: CrosshairMode.Normal,
|
||||
},
|
||||
localization: {
|
||||
priceFormatter: valueToString,
|
||||
locale: "en-us",
|
||||
},
|
||||
};
|
||||
|
||||
let chart: IChartApi;
|
||||
|
||||
if (scale === "date") {
|
||||
chart = createClassicChart(element, options);
|
||||
} else {
|
||||
const horzScaleBehavior = new HorzScaleBehaviorHeight();
|
||||
|
||||
// @ts-ignore
|
||||
chart = createCustomChart(element, horzScaleBehavior, options);
|
||||
}
|
||||
|
||||
chart.priceScale("right").applyOptions({
|
||||
...priceScaleOptions,
|
||||
scaleMargins: {
|
||||
top: 0.05,
|
||||
bottom: 0.05,
|
||||
...priceScaleOptions?.scaleMargins,
|
||||
},
|
||||
minimumWidth: 78,
|
||||
});
|
||||
|
||||
createEffect(() => {
|
||||
const { white } = colors;
|
||||
|
||||
const textColor = white(dark);
|
||||
const borderColor = dark() ? "#332F24" : "#F1E4E0";
|
||||
|
||||
chart.applyOptions({
|
||||
layout: {
|
||||
textColor,
|
||||
},
|
||||
rightPriceScale: {
|
||||
borderColor,
|
||||
},
|
||||
timeScale: {
|
||||
borderColor,
|
||||
},
|
||||
crosshair: {
|
||||
horzLine: {
|
||||
color: textColor,
|
||||
labelBackgroundColor: textColor,
|
||||
},
|
||||
vertLine: {
|
||||
color: textColor,
|
||||
labelBackgroundColor: textColor,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
return chart;
|
||||
}
|
||||
+13
-5
@@ -1,14 +1,22 @@
|
||||
import { PRICE_SCALE_MOMENTUM_ID } from "../../chart/price";
|
||||
import { defaultSeriesOptions } from "./options";
|
||||
|
||||
type HistogramOptions = DeepPartial<
|
||||
HistogramStyleOptions & SeriesOptionsCommon
|
||||
>;
|
||||
|
||||
export const createHistogramSeries = (
|
||||
chart: IChartApi,
|
||||
options?: HistogramOptions,
|
||||
) => {
|
||||
export const PRICE_SCALE_MOMENTUM_ID = "momentum";
|
||||
|
||||
export const createHistogramSeries = ({
|
||||
chart,
|
||||
// dark,
|
||||
// color,
|
||||
options,
|
||||
}: {
|
||||
chart: IChartApi;
|
||||
// dark: Accessor<boolean>;
|
||||
// color: Color;
|
||||
options?: HistogramOptions;
|
||||
}) => {
|
||||
const seriesOptions: HistogramOptions = {
|
||||
priceScaleId: "left",
|
||||
...defaultSeriesOptions,
|
||||
+1
@@ -9,6 +9,7 @@ export class HorzScaleBehaviorHeight implements IHorzScaleBehavior<number> {
|
||||
setOptions() {}
|
||||
preprocessData() {}
|
||||
updateFormatter() {}
|
||||
|
||||
createConverterToInternalObj() {
|
||||
return (price) => price;
|
||||
}
|
||||
+15
-18
@@ -8,29 +8,26 @@ import {
|
||||
} from "/src/scripts/utils/urlParams";
|
||||
import { createRWS } from "/src/solid/rws";
|
||||
|
||||
import { chartState } from "../../chart/state";
|
||||
import { setTimeScale } from "../../chart/time";
|
||||
|
||||
export function createSeriesLegend({
|
||||
export function createSeriesLegend<Scale extends ResourceScale>({
|
||||
id,
|
||||
presetId,
|
||||
title,
|
||||
color,
|
||||
series,
|
||||
seriesList,
|
||||
defaultVisible = true,
|
||||
disabled: _disabled,
|
||||
visible: _visible,
|
||||
url,
|
||||
dataset,
|
||||
}: {
|
||||
id: string;
|
||||
presetId: string;
|
||||
title: string;
|
||||
color: Accessor<string | string[]>;
|
||||
series: ISeriesApi<SeriesType>;
|
||||
color: Color | Color[];
|
||||
seriesList: Accessor<ISeriesApi<SeriesType> | undefined>[];
|
||||
defaultVisible?: boolean;
|
||||
disabled?: Accessor<boolean>;
|
||||
visible?: RWS<boolean>;
|
||||
url?: string;
|
||||
dataset: ResourceDataset<Scale>;
|
||||
}) {
|
||||
const storageID = `${presetId}-${id}`;
|
||||
|
||||
@@ -44,15 +41,14 @@ export function createSeriesLegend({
|
||||
|
||||
const disabled = createMemo(_disabled || (() => false));
|
||||
|
||||
const drawn = createMemo(() => visible() && !disabled());
|
||||
|
||||
createEffect(() => {
|
||||
if (disabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const v = visible();
|
||||
const d = disabled();
|
||||
|
||||
series.applyOptions({
|
||||
visible: !d && v,
|
||||
});
|
||||
|
||||
setTimeScale(chartState.range);
|
||||
|
||||
if (v !== defaultVisible) {
|
||||
writeURLParam(id, v);
|
||||
@@ -66,10 +62,11 @@ export function createSeriesLegend({
|
||||
return {
|
||||
id,
|
||||
title,
|
||||
series,
|
||||
seriesList,
|
||||
color,
|
||||
visible,
|
||||
disabled,
|
||||
url,
|
||||
drawn,
|
||||
dataset,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { defaultSeriesOptions } from "./options";
|
||||
|
||||
export const createLineSeries = ({
|
||||
chart,
|
||||
dark,
|
||||
color,
|
||||
options,
|
||||
}: {
|
||||
chart: IChartApi;
|
||||
dark: Accessor<boolean>;
|
||||
color: Color;
|
||||
options?: DeepPartialLineOptions;
|
||||
}) => {
|
||||
const series = chart.addLineSeries({
|
||||
...defaultSeriesOptions,
|
||||
...options,
|
||||
});
|
||||
|
||||
createEffect(() => {
|
||||
series.applyOptions({
|
||||
color: color(dark),
|
||||
});
|
||||
});
|
||||
|
||||
return series;
|
||||
};
|
||||
@@ -0,0 +1,135 @@
|
||||
import { colors } from "/src/scripts/utils/colors";
|
||||
|
||||
import { chunkIdToIndex } from "../datasets/resource";
|
||||
import { valueToString } from "../utils/locale";
|
||||
|
||||
export function setMinMaxMarkers({
|
||||
scale,
|
||||
visibleRange,
|
||||
legendList,
|
||||
activeIds,
|
||||
dark,
|
||||
}: {
|
||||
scale: ResourceScale;
|
||||
visibleRange: TimeRange | undefined;
|
||||
legendList: SeriesLegend[];
|
||||
activeIds: Accessor<number[]>;
|
||||
dark: Accessor<boolean>;
|
||||
}) {
|
||||
try {
|
||||
if (!visibleRange) return;
|
||||
|
||||
const { from, to } = visibleRange;
|
||||
|
||||
const dateFrom = new Date(from as string);
|
||||
const dateTo = new Date(to as string);
|
||||
|
||||
let max = undefined as [number, Time, number, ISeriesApi<any>] | undefined;
|
||||
let min = undefined as [number, Time, number, ISeriesApi<any>] | undefined;
|
||||
|
||||
const ids = activeIds();
|
||||
|
||||
for (let i = 0; i < legendList.length; i++) {
|
||||
const { seriesList, dataset } = legendList[i];
|
||||
|
||||
for (let j = 0; j < ids.length; j++) {
|
||||
const id = ids[j];
|
||||
|
||||
const seriesIndex = chunkIdToIndex(scale, id);
|
||||
|
||||
const series = seriesList.at(seriesIndex)?.();
|
||||
|
||||
if (!series || !series?.options().visible) continue;
|
||||
|
||||
series.setMarkers([]);
|
||||
|
||||
const isCandlestick = series.seriesType() === "Candlestick";
|
||||
|
||||
const vec = dataset.fetchedJSONs.at(seriesIndex)?.vec();
|
||||
|
||||
if (!vec) return;
|
||||
|
||||
for (let k = 0; k < vec.length; k++) {
|
||||
const data = vec[k];
|
||||
|
||||
let number;
|
||||
|
||||
if (scale === "date") {
|
||||
const date =
|
||||
typeof data.time === "string"
|
||||
? new Date(data.time)
|
||||
: // @ts-ignore
|
||||
new Date(data.time.year, data.time.month, data.time.day);
|
||||
|
||||
number = date.getTime();
|
||||
|
||||
if (date <= dateFrom || date >= dateTo) {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
const height = data.time;
|
||||
|
||||
number = height as number;
|
||||
|
||||
if (height <= from || height >= to) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
const high = isCandlestick ? data["high"] : data.value;
|
||||
// @ts-ignore
|
||||
const low = isCandlestick ? data["low"] : data.value;
|
||||
|
||||
if (!max || high > max[2]) {
|
||||
max = [number, data.time, high, series];
|
||||
}
|
||||
if (!min || low < min[2]) {
|
||||
min = [number, data.time, low, series];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let minMarker: (SeriesMarker<Time> & { weight: number }) | undefined;
|
||||
let maxMarker: (SeriesMarker<Time> & { weight: number }) | undefined;
|
||||
|
||||
if (min) {
|
||||
minMarker = {
|
||||
weight: min[0],
|
||||
time: min[1],
|
||||
color: colors.white(dark),
|
||||
position: "belowBar" as const,
|
||||
shape: "arrowUp" as const,
|
||||
size: 0,
|
||||
text: valueToString(min[2]),
|
||||
};
|
||||
}
|
||||
|
||||
if (max) {
|
||||
maxMarker = {
|
||||
weight: max[0],
|
||||
time: max[1],
|
||||
color: colors.white(dark),
|
||||
position: "aboveBar" as const,
|
||||
shape: "arrowDown" as const,
|
||||
size: 0,
|
||||
text: valueToString(max[2]),
|
||||
};
|
||||
}
|
||||
|
||||
if (min && max && min[3] === max[3] && minMarker && maxMarker) {
|
||||
min[3].setMarkers(
|
||||
[minMarker, maxMarker].sort((a, b) => a.weight - b.weight),
|
||||
);
|
||||
} else {
|
||||
if (min && minMarker) {
|
||||
min[3].setMarkers([minMarker]);
|
||||
}
|
||||
|
||||
if (max && maxMarker) {
|
||||
max[3].setMarkers([maxMarker]);
|
||||
}
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
import { defaultSeriesOptions } from "./options";
|
||||
|
||||
type AreaOptions = DeepPartial<AreaStyleOptions & SeriesOptionsCommon>;
|
||||
|
||||
export const createAreaSeries = (
|
||||
chart: IChartApi,
|
||||
options?: AreaOptions & {
|
||||
color?: string;
|
||||
},
|
||||
) => {
|
||||
const { color } = options || {};
|
||||
|
||||
// const fillColor = `${color}11`;
|
||||
const fillColor = color;
|
||||
|
||||
const seriesOptions: AreaOptions = {
|
||||
// priceScaleId: 'left',
|
||||
...defaultSeriesOptions,
|
||||
lineColor: color,
|
||||
topColor: fillColor,
|
||||
bottomColor: fillColor,
|
||||
...options,
|
||||
};
|
||||
|
||||
const series = chart.addAreaSeries(seriesOptions);
|
||||
|
||||
return series;
|
||||
};
|
||||
@@ -1,52 +0,0 @@
|
||||
import { colors } from "/src/scripts/utils/colors";
|
||||
|
||||
import { defaultSeriesOptions } from "./options";
|
||||
|
||||
const DEFAULT_BASELINE_TOP_COLOR = colors.profit;
|
||||
const DEFAULT_BASELINE_BOTTOM_COLOR = colors.loss;
|
||||
|
||||
export const DEFAULT_BASELINE_COLORS = [
|
||||
DEFAULT_BASELINE_TOP_COLOR,
|
||||
DEFAULT_BASELINE_BOTTOM_COLOR,
|
||||
];
|
||||
|
||||
export const createBaseLineSeries = (
|
||||
chart: IChartApi,
|
||||
options: BaselineSeriesOptions,
|
||||
) => {
|
||||
const {
|
||||
title,
|
||||
color,
|
||||
topColor,
|
||||
topLineColor,
|
||||
bottomColor,
|
||||
bottomLineColor,
|
||||
base,
|
||||
lineColor,
|
||||
} = options;
|
||||
|
||||
const allTopColor = topColor || color || DEFAULT_BASELINE_TOP_COLOR;
|
||||
const topFillColor = `${allTopColor}`;
|
||||
const allBottomColor = bottomColor || color || DEFAULT_BASELINE_BOTTOM_COLOR;
|
||||
const bottomFillColor = `${allBottomColor}`;
|
||||
|
||||
const seriesOptions: DeepPartialBaselineOptions = {
|
||||
priceScaleId: "right",
|
||||
...defaultSeriesOptions,
|
||||
lineWidth: 1,
|
||||
...options,
|
||||
...options.options,
|
||||
...(base ? { baseValue: { type: "price", price: base } } : {}),
|
||||
topLineColor: topLineColor || lineColor || allTopColor,
|
||||
topFillColor1: topFillColor,
|
||||
topFillColor2: topFillColor,
|
||||
bottomLineColor: bottomLineColor || lineColor || allBottomColor,
|
||||
bottomFillColor1: bottomFillColor,
|
||||
bottomFillColor2: bottomFillColor,
|
||||
title,
|
||||
};
|
||||
|
||||
const series = chart.addBaselineSeries(seriesOptions);
|
||||
|
||||
return series;
|
||||
};
|
||||
@@ -1,42 +0,0 @@
|
||||
import { colors } from "/src/scripts/utils/colors";
|
||||
|
||||
export const createCandlesticksSeries = (
|
||||
chart: IChartApi,
|
||||
options: PriceSeriesOptions,
|
||||
): [ISeriesApi<"Candlestick">, string[]] => {
|
||||
const { inverseColors, lowerOpacity } = options;
|
||||
|
||||
const upColor = lowerOpacity
|
||||
? inverseColors
|
||||
? colors.darkLoss
|
||||
: colors.darkProfit
|
||||
: inverseColors
|
||||
? colors.loss
|
||||
: colors.profit;
|
||||
|
||||
const downColor = lowerOpacity
|
||||
? inverseColors
|
||||
? colors.darkProfit
|
||||
: colors.darkLoss
|
||||
: inverseColors
|
||||
? colors.profit
|
||||
: colors.loss;
|
||||
|
||||
const candlestickSeries = chart.addCandlestickSeries({
|
||||
baseLineVisible: false,
|
||||
upColor,
|
||||
wickUpColor: upColor,
|
||||
downColor,
|
||||
wickDownColor: downColor,
|
||||
borderVisible: false,
|
||||
priceLineVisible: false,
|
||||
baseLineColor: "",
|
||||
borderColor: "",
|
||||
borderDownColor: "",
|
||||
borderUpColor: "",
|
||||
// lastValueVisible: false,
|
||||
...options.seriesOptions,
|
||||
});
|
||||
|
||||
return [candlestickSeries, [upColor, downColor]];
|
||||
};
|
||||
@@ -1,10 +0,0 @@
|
||||
import { defaultSeriesOptions } from "./options";
|
||||
|
||||
export const createLineSeries = (
|
||||
chart: IChartApi,
|
||||
options?: DeepPartialLineOptions,
|
||||
) =>
|
||||
chart.addLineSeries({
|
||||
...defaultSeriesOptions,
|
||||
...options,
|
||||
});
|
||||
@@ -1,45 +0,0 @@
|
||||
export const resetRightPriceScale = (
|
||||
chart: IChartApi,
|
||||
options?: FullPriceScaleOptions,
|
||||
) => {
|
||||
const finalOptions = {
|
||||
...options,
|
||||
scaleMargins: {
|
||||
...(options?.halved
|
||||
? {
|
||||
top: 0.5,
|
||||
bottom: 0.05,
|
||||
}
|
||||
: {
|
||||
top: 0.1,
|
||||
bottom: 0.1,
|
||||
}),
|
||||
...options?.scaleMargins,
|
||||
},
|
||||
};
|
||||
|
||||
chart.priceScale("right").applyOptions(finalOptions);
|
||||
|
||||
return finalOptions;
|
||||
};
|
||||
|
||||
export const resetLeftPriceScale = (
|
||||
chart: IChartApi,
|
||||
options?: FullPriceScaleOptions,
|
||||
) =>
|
||||
chart.priceScale("left").applyOptions({
|
||||
visible: false,
|
||||
...options,
|
||||
scaleMargins: {
|
||||
...(options?.halved
|
||||
? {
|
||||
top: 0.475,
|
||||
bottom: 0.025,
|
||||
}
|
||||
: {
|
||||
top: 0.25,
|
||||
bottom: 0.25,
|
||||
}),
|
||||
...options?.scaleMargins,
|
||||
},
|
||||
});
|
||||
@@ -1,3 +0,0 @@
|
||||
interface FullPriceScaleOptions extends DeepPartial<PriceScaleOptions> {
|
||||
halved?: boolean;
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
import { HEIGHT_CHUNK_SIZE } from "../datasets";
|
||||
import { debounce } from "../utils/debounce";
|
||||
import { run } from "../utils/run";
|
||||
import { tick } from "../utils/tick";
|
||||
import { writeURLParam } from "../utils/urlParams";
|
||||
|
||||
const LOCAL_STORAGE_RANGE_KEY = "chart-range";
|
||||
const URL_PARAMS_RANGE_FROM_KEY = "from";
|
||||
const URL_PARAMS_RANGE_TO_KEY = "to";
|
||||
|
||||
export function getInitialTimeRange(scale: ResourceScale): TimeRange {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
|
||||
const urlFrom = urlParams.get(URL_PARAMS_RANGE_FROM_KEY);
|
||||
const urlTo = urlParams.get(URL_PARAMS_RANGE_TO_KEY);
|
||||
|
||||
if (urlFrom && urlTo) {
|
||||
if (scale === "date" && urlFrom.includes("-") && urlTo.includes("-")) {
|
||||
return {
|
||||
from: new Date(urlFrom).toJSON().split("T")[0],
|
||||
to: new Date(urlTo).toJSON().split("T")[0],
|
||||
} satisfies TimeRange;
|
||||
} else if (
|
||||
scale === "height" &&
|
||||
!urlFrom.includes("-") &&
|
||||
!urlTo.includes("-")
|
||||
) {
|
||||
return {
|
||||
from: Number(urlFrom),
|
||||
to: Number(urlTo),
|
||||
} as any satisfies TimeRange;
|
||||
}
|
||||
}
|
||||
|
||||
const savedTimeRange = JSON.parse(
|
||||
localStorage.getItem(getLocalStorageKey(scale)) || "null",
|
||||
) as TimeRange | null;
|
||||
|
||||
if (savedTimeRange) {
|
||||
return savedTimeRange;
|
||||
}
|
||||
|
||||
switch (scale) {
|
||||
case "date": {
|
||||
const defaultTo = new Date();
|
||||
const defaultFrom = new Date();
|
||||
defaultFrom.setDate(defaultFrom.getUTCDate() - 6 * 30);
|
||||
|
||||
return {
|
||||
from: defaultFrom.toJSON().split("T")[0],
|
||||
to: defaultTo.toJSON().split("T")[0],
|
||||
} satisfies TimeRange;
|
||||
}
|
||||
case "height": {
|
||||
return {
|
||||
from: 800_000,
|
||||
to: 850_000,
|
||||
} as any satisfies TimeRange;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function initTimeScale({
|
||||
scale,
|
||||
activeIds,
|
||||
exactRange,
|
||||
charts,
|
||||
}: {
|
||||
scale: ResourceScale;
|
||||
activeIds: RWS<number[]>;
|
||||
exactRange: RWS<TimeRange>;
|
||||
charts: ChartObject[];
|
||||
}) {
|
||||
const firstChart = charts.at(0)?.chart;
|
||||
|
||||
if (!firstChart) return;
|
||||
|
||||
firstChart.timeScale().subscribeVisibleTimeRangeChange((range) => {
|
||||
if (!range) return;
|
||||
|
||||
exactRange.set(range);
|
||||
|
||||
debouncedsetActiveIds({ exactRange: range, activeIds: activeIds });
|
||||
|
||||
debouncedSaveTimeRange({ scale, range });
|
||||
});
|
||||
|
||||
const range = exactRange();
|
||||
|
||||
run(async () => {
|
||||
if (range) {
|
||||
await tick();
|
||||
firstChart.timeScale().setVisibleRange(range);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function getLocalStorageKey(scale: ResourceScale) {
|
||||
return `${LOCAL_STORAGE_RANGE_KEY}-${scale}`;
|
||||
}
|
||||
|
||||
export function setActiveIds({
|
||||
exactRange,
|
||||
activeIds,
|
||||
}: {
|
||||
exactRange: TimeRange;
|
||||
activeIds: RWS<number[]>;
|
||||
}) {
|
||||
let ids: number[] = [];
|
||||
|
||||
const today = new Date();
|
||||
|
||||
if (
|
||||
typeof exactRange.from === "string" &&
|
||||
typeof exactRange.to === "string"
|
||||
) {
|
||||
const from = new Date(exactRange.from).getUTCFullYear();
|
||||
const to = new Date(exactRange.to).getUTCFullYear();
|
||||
|
||||
ids = Array.from({ length: to - from + 1 }, (_, i) => i + from).filter(
|
||||
(year) => year >= 2009 && year <= today.getUTCFullYear(),
|
||||
);
|
||||
} else {
|
||||
const from = Math.floor(Number(exactRange.from) / HEIGHT_CHUNK_SIZE);
|
||||
const to = Math.floor(Number(exactRange.to) / HEIGHT_CHUNK_SIZE);
|
||||
|
||||
const length = to - from + 1;
|
||||
|
||||
ids = Array.from({ length }, (_, i) => (from + i) * HEIGHT_CHUNK_SIZE);
|
||||
}
|
||||
|
||||
const old = activeIds();
|
||||
|
||||
if (
|
||||
old.length !== ids.length ||
|
||||
old.at(0) !== ids.at(0) ||
|
||||
old.at(-1) !== ids.at(-1)
|
||||
) {
|
||||
console.log("range:", ids);
|
||||
|
||||
activeIds.set(ids);
|
||||
}
|
||||
}
|
||||
|
||||
const debouncedsetActiveIds = debounce(setActiveIds, 100);
|
||||
|
||||
function saveTimeRange({
|
||||
scale,
|
||||
range,
|
||||
}: {
|
||||
scale: ResourceScale;
|
||||
range: TimeRange;
|
||||
}) {
|
||||
writeURLParam(URL_PARAMS_RANGE_FROM_KEY, String(range.from));
|
||||
|
||||
writeURLParam(URL_PARAMS_RANGE_TO_KEY, String(range.to));
|
||||
|
||||
localStorage.setItem(getLocalStorageKey(scale), JSON.stringify(range));
|
||||
}
|
||||
|
||||
const debouncedSaveTimeRange = debounce(saveTimeRange, 500);
|
||||
+9
@@ -1,3 +1,12 @@
|
||||
interface PriceSeriesOptions {
|
||||
placement?: "top" | "bottom";
|
||||
title?: string;
|
||||
id?: string;
|
||||
inverseColors?: boolean;
|
||||
seriesOptions?: DeepPartial<SeriesOptionsCommon>;
|
||||
priceScaleOptions?: DeepPartial<PriceScaleOptions>;
|
||||
}
|
||||
|
||||
interface BaselineSeriesOptions {
|
||||
color?: string;
|
||||
topColor?: string;
|
||||
@@ -0,0 +1,143 @@
|
||||
import { dateToString, getNumberOfDaysBetweenTwoDates } from "../utils/date";
|
||||
import { createLineSeries } from "./line";
|
||||
|
||||
export const GENESIS_DAY = "2009-01-03";
|
||||
|
||||
const whitespaceStartDate = new Date("1970-01-01");
|
||||
const whitespaceStartDateYear = whitespaceStartDate.getUTCFullYear();
|
||||
const whitespaceStartDateMonth = whitespaceStartDate.getUTCMonth();
|
||||
const whitespaceStartDateDate = whitespaceStartDate.getUTCDate();
|
||||
const whitespaceEndDate = new Date("2141-01-01");
|
||||
const whitespaceDateDataset: (WhitespaceData | SingleValueData)[] = new Array(
|
||||
getNumberOfDaysBetweenTwoDates(whitespaceStartDate, whitespaceEndDate),
|
||||
);
|
||||
// Hack to be able to scroll freely
|
||||
// Setting them all to NaN is much slower
|
||||
for (let i = 0; i < whitespaceDateDataset.length; i++) {
|
||||
const date = new Date(
|
||||
whitespaceStartDateYear,
|
||||
whitespaceStartDateMonth,
|
||||
whitespaceStartDateDate + i,
|
||||
);
|
||||
|
||||
const time = dateToString(date);
|
||||
|
||||
if (i === whitespaceDateDataset.length - 1) {
|
||||
whitespaceDateDataset[i] = {
|
||||
time,
|
||||
value: NaN,
|
||||
};
|
||||
} else {
|
||||
whitespaceDateDataset[i] = {
|
||||
time,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const heightStart = -50_000;
|
||||
const whitespaceHeightDataset: WhitespaceData[] = new Array(
|
||||
(new Date().getUTCFullYear() - 2009 + 1) * 60_000,
|
||||
);
|
||||
for (let i = 0; i < whitespaceHeightDataset.length; i++) {
|
||||
const height = heightStart + i;
|
||||
|
||||
whitespaceHeightDataset[i] = {
|
||||
time: height as any,
|
||||
};
|
||||
}
|
||||
|
||||
export function setWhitespace(chart: IChartApi, scale: ResourceScale) {
|
||||
const whitespace = chart.addLineSeries();
|
||||
|
||||
if (scale === "date") {
|
||||
whitespace.setData(whitespaceDateDataset);
|
||||
} else {
|
||||
whitespace.setData(whitespaceHeightDataset);
|
||||
|
||||
const time = whitespaceHeightDataset.length;
|
||||
whitespace.update({
|
||||
time: time as Time,
|
||||
value: NaN,
|
||||
});
|
||||
}
|
||||
|
||||
return whitespace;
|
||||
}
|
||||
|
||||
// ---
|
||||
|
||||
// import { HEIGHT_CHUNK_SIZE } from "../datasets";
|
||||
// import { dateToString } from "../utils/date";
|
||||
|
||||
// export const GENESIS_DAY = "2009-01-03";
|
||||
|
||||
// function leapYear(year: number) {
|
||||
// return (year % 4 == 0 && year % 100 != 0) || year % 400 == 0;
|
||||
// }
|
||||
|
||||
// const whitespaceStartDate = new Date("1970-01-01");
|
||||
// export const whitespaceStartDateYear = whitespaceStartDate.getFullYear();
|
||||
// const whitespaceStartDateMonth = whitespaceStartDate.getMonth();
|
||||
// const whitespaceStartDateDate = whitespaceStartDate.getDate();
|
||||
// const whitespaceEndDate = new Date("2141-01-01");
|
||||
// const whitespaceEndDateYear = whitespaceEndDate.getFullYear();
|
||||
|
||||
// export const whitespaceDateDatasets: (WhitespaceData | SingleValueData)[][] =
|
||||
// Array.from(
|
||||
// { length: whitespaceEndDateYear - whitespaceStartDateYear },
|
||||
// (_, i) => new Array(leapYear(whitespaceStartDateYear + i) ? 366 : 365),
|
||||
// );
|
||||
// for (let i = 0; i < whitespaceDateDatasets.length; i++) {
|
||||
// const year = whitespaceStartDateYear + i;
|
||||
// const whitespaceDateDataset = whitespaceDateDatasets[i];
|
||||
|
||||
// // Hack to be able to scroll freely
|
||||
// // Setting them all to NaN is much slower
|
||||
// for (let j = 0; j < whitespaceDateDataset.length; j++) {
|
||||
// const date = new Date(
|
||||
// year,
|
||||
// whitespaceStartDateMonth,
|
||||
// whitespaceStartDateDate + j,
|
||||
// );
|
||||
|
||||
// const time = dateToString(date);
|
||||
|
||||
// if (j === whitespaceDateDataset.length - 1) {
|
||||
// whitespaceDateDataset[j] = {
|
||||
// time,
|
||||
// value: NaN,
|
||||
// };
|
||||
// } else {
|
||||
// whitespaceDateDataset[j] = {
|
||||
// time,
|
||||
// };
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// export const whitespaceHeightStart = -50_000;
|
||||
// export const whitespaceHeightDatasets: (WhitespaceData | SingleValueData)[][] =
|
||||
// Array.from(
|
||||
// { length: (new Date().getUTCFullYear() - 2009 + 1) * 6 },
|
||||
// () => new Array(HEIGHT_CHUNK_SIZE),
|
||||
// );
|
||||
|
||||
// for (let i = 0; i < whitespaceHeightDatasets.length; i++) {
|
||||
// const offset = HEIGHT_CHUNK_SIZE * i;
|
||||
// const whitespaceHeightDataset = whitespaceHeightDatasets[i];
|
||||
|
||||
// for (let j = 0; j < whitespaceHeightDataset.length; j++) {
|
||||
// const height = whitespaceHeightStart + offset + j;
|
||||
|
||||
// if (j === whitespaceHeightDataset.length - 1) {
|
||||
// whitespaceHeightDataset[j] = {
|
||||
// time: height as any,
|
||||
// value: NaN,
|
||||
// };
|
||||
// } else {
|
||||
// whitespaceHeightDataset[j] = {
|
||||
// time: height as any,
|
||||
// };
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
@@ -4,8 +4,8 @@ import {
|
||||
} from "../../datasets/consts/address";
|
||||
import { liquidities } from "../../datasets/consts/liquidities";
|
||||
import { colors } from "../../utils/colors";
|
||||
import { applySeriesList, SeriesType } from "../apply";
|
||||
import { createCohortPresetList } from "../templates/cohort";
|
||||
import { applyMultipleSeries, SeriesType } from "../templates/multiple";
|
||||
|
||||
export function createPresets({
|
||||
scale,
|
||||
@@ -22,16 +22,12 @@ export function createPresets({
|
||||
description: "",
|
||||
icon: IconTablerWallet,
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: `Total Non Empty Address`,
|
||||
color: colors.bitcoin,
|
||||
seriesType: SeriesType.Area,
|
||||
dataset: params.datasets[scale].address_count,
|
||||
},
|
||||
],
|
||||
@@ -45,12 +41,9 @@ export function createPresets({
|
||||
description: "",
|
||||
icon: IconTablerSparkles,
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: `New Addresses`,
|
||||
color: colors.white,
|
||||
@@ -67,16 +60,12 @@ export function createPresets({
|
||||
description: "",
|
||||
icon: IconTablerArchive,
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: `Total Addresses Created`,
|
||||
color: colors.bitcoin,
|
||||
seriesType: SeriesType.Area,
|
||||
dataset: params.datasets[scale].created_addresses,
|
||||
},
|
||||
],
|
||||
@@ -90,16 +79,12 @@ export function createPresets({
|
||||
description: "",
|
||||
icon: IconTablerTrash,
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: `Total Empty Addresses`,
|
||||
color: colors.darkWhite,
|
||||
seriesType: SeriesType.Area,
|
||||
dataset: params.datasets[scale].empty_addresses,
|
||||
},
|
||||
],
|
||||
@@ -142,7 +127,7 @@ function createAddressPresetFolder<Scale extends ResourceScale>({
|
||||
scale: Scale;
|
||||
name: string;
|
||||
datasetKey: AddressCohortKey;
|
||||
color: string;
|
||||
color: Color;
|
||||
}): PartialPresetFolder {
|
||||
return {
|
||||
name,
|
||||
@@ -155,25 +140,46 @@ function createAddressPresetFolder<Scale extends ResourceScale>({
|
||||
color,
|
||||
datasetKey,
|
||||
}),
|
||||
{
|
||||
name: `Split By Liquidity`,
|
||||
tree: liquidities.map(
|
||||
(liquidity): PartialPresetFolder => ({
|
||||
name: liquidity.name,
|
||||
tree: createCohortPresetList({
|
||||
title: `${liquidity.name} ${name}`,
|
||||
name: `${liquidity.name} ${name}`,
|
||||
scale,
|
||||
color,
|
||||
datasetKey: `${liquidity.key}_${datasetKey}`,
|
||||
}),
|
||||
}),
|
||||
),
|
||||
},
|
||||
createLiquidityFolder({
|
||||
scale,
|
||||
name,
|
||||
datasetKey,
|
||||
color,
|
||||
}),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
export function createLiquidityFolder<Scale extends ResourceScale>({
|
||||
scale,
|
||||
color,
|
||||
name,
|
||||
datasetKey,
|
||||
}: {
|
||||
scale: Scale;
|
||||
name: string;
|
||||
datasetKey: AddressCohortKey | "";
|
||||
color: Color;
|
||||
}): PartialPresetFolder {
|
||||
return {
|
||||
name: `Split By Liquidity`,
|
||||
tree: liquidities.map(
|
||||
(liquidity): PartialPresetFolder => ({
|
||||
name: liquidity.name,
|
||||
tree: createCohortPresetList({
|
||||
title: `${liquidity.name} ${name}`,
|
||||
name: `${liquidity.name} ${name}`,
|
||||
scale,
|
||||
color,
|
||||
datasetKey: !datasetKey
|
||||
? liquidity.key
|
||||
: `${liquidity.key}_${datasetKey}`,
|
||||
}),
|
||||
}),
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
export function createAddressCountPreset<Scale extends ResourceScale>({
|
||||
scale,
|
||||
color,
|
||||
@@ -183,7 +189,7 @@ export function createAddressCountPreset<Scale extends ResourceScale>({
|
||||
scale: Scale;
|
||||
name: string;
|
||||
datasetKey: AddressCohortKey;
|
||||
color: string;
|
||||
color: Color;
|
||||
}): PartialPreset {
|
||||
return {
|
||||
scale,
|
||||
@@ -191,12 +197,9 @@ export function createAddressCountPreset<Scale extends ResourceScale>({
|
||||
title: `${name} Address Count`,
|
||||
icon: IconTablerAddressBook,
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: "Address Count",
|
||||
color,
|
||||
|
||||
@@ -0,0 +1,661 @@
|
||||
import { requestIdleCallbackPossible } from "/src/env";
|
||||
import { createRWS } from "/src/solid/rws";
|
||||
|
||||
import { chunkIdToIndex } from "../datasets/resource";
|
||||
import {
|
||||
createBaseLineSeries,
|
||||
DEFAULT_BASELINE_COLORS,
|
||||
} from "../lightweightCharts/baseLine";
|
||||
import { createCandlesticksSeries } from "../lightweightCharts/candlesticks";
|
||||
import { createChart } from "../lightweightCharts/create";
|
||||
import { createHistogramSeries } from "../lightweightCharts/histogram";
|
||||
import { createSeriesLegend } from "../lightweightCharts/legend";
|
||||
import { createLineSeries } from "../lightweightCharts/line";
|
||||
import { setMinMaxMarkers } from "../lightweightCharts/markers";
|
||||
import {
|
||||
getInitialTimeRange,
|
||||
initTimeScale,
|
||||
setActiveIds,
|
||||
} from "../lightweightCharts/time";
|
||||
import { setWhitespace } from "../lightweightCharts/whitespace";
|
||||
import { colors } from "../utils/colors";
|
||||
import { debounce } from "../utils/debounce";
|
||||
import { stringToId } from "../utils/id";
|
||||
import { webSockets } from "../ws";
|
||||
|
||||
export enum SeriesType {
|
||||
Line,
|
||||
Based,
|
||||
Histogram,
|
||||
Candlestick,
|
||||
}
|
||||
|
||||
type SeriesConfig<Scale extends ResourceScale> =
|
||||
| {
|
||||
dataset: ResourceDataset<Scale>;
|
||||
color?: Color;
|
||||
topColor?: Color;
|
||||
bottomColor?: Color;
|
||||
colors?: undefined;
|
||||
seriesType: SeriesType.Based;
|
||||
title: string;
|
||||
options?: BaselineSeriesOptions;
|
||||
priceScaleOptions?: DeepPartialPriceScaleOptions;
|
||||
defaultVisible?: boolean;
|
||||
}
|
||||
| {
|
||||
dataset: ResourceDataset<Scale>;
|
||||
color?: Color;
|
||||
colors?: Color[];
|
||||
seriesType: SeriesType.Histogram;
|
||||
title: string;
|
||||
options?: DeepPartialHistogramOptions;
|
||||
priceScaleOptions?: DeepPartialPriceScaleOptions;
|
||||
defaultVisible?: boolean;
|
||||
}
|
||||
| {
|
||||
dataset: ResourceDataset<Scale>;
|
||||
seriesType: SeriesType.Candlestick;
|
||||
priceScaleOptions?: DeepPartialPriceScaleOptions;
|
||||
colors?: undefined;
|
||||
color?: undefined;
|
||||
options?: DeepPartialLineOptions;
|
||||
defaultVisible?: boolean;
|
||||
title: string;
|
||||
}
|
||||
| {
|
||||
dataset: ResourceDataset<Scale>;
|
||||
color: Color;
|
||||
colors?: undefined;
|
||||
seriesType?: SeriesType.Line;
|
||||
title: string;
|
||||
options?: DeepPartialLineOptions;
|
||||
priceScaleOptions?: DeepPartialPriceScaleOptions;
|
||||
defaultVisible?: boolean;
|
||||
};
|
||||
|
||||
export function applySeriesList<Scale extends ResourceScale>({
|
||||
parentDiv,
|
||||
charts: reactiveChartList,
|
||||
top,
|
||||
bottom,
|
||||
preset,
|
||||
priceScaleOptions,
|
||||
datasets,
|
||||
priceDataset,
|
||||
priceOptions,
|
||||
legendSetter,
|
||||
dark,
|
||||
activeIds,
|
||||
}: {
|
||||
charts: RWS<IChartApi[]>;
|
||||
parentDiv: HTMLDivElement;
|
||||
preset: Preset;
|
||||
legendSetter: Setter<SeriesLegend[]>;
|
||||
priceDataset?: ResourceDataset<Scale>;
|
||||
priceOptions?: PriceSeriesOptions;
|
||||
priceScaleOptions?: DeepPartialPriceScaleOptions;
|
||||
top?: SeriesConfig<Scale>[];
|
||||
bottom?: SeriesConfig<Scale>[];
|
||||
datasets: Datasets;
|
||||
dark: Accessor<boolean>;
|
||||
activeIds: RWS<number[]>;
|
||||
}) {
|
||||
// ---
|
||||
// Reset states
|
||||
// ---
|
||||
|
||||
legendSetter([]);
|
||||
|
||||
reactiveChartList.set((charts) => {
|
||||
charts.forEach((chart) => {
|
||||
chart.remove();
|
||||
});
|
||||
|
||||
return [];
|
||||
});
|
||||
|
||||
parentDiv.replaceChildren();
|
||||
|
||||
// ---
|
||||
// Done
|
||||
// ---
|
||||
|
||||
const scale = preset.scale;
|
||||
|
||||
const presetLegend: SeriesLegend[] = [];
|
||||
|
||||
const priceSeriesType = createRWS<PriceSeriesType>("Candlestick");
|
||||
|
||||
const activeDatasets: ResourceDataset<any, any>[] = [];
|
||||
|
||||
const lastActiveIndex = createMemo(() => {
|
||||
const last = activeIds().at(-1);
|
||||
return last !== undefined ? chunkIdToIndex(scale, last) : undefined;
|
||||
});
|
||||
|
||||
const exactRange = createRWS(getInitialTimeRange(scale));
|
||||
|
||||
setActiveIds({
|
||||
exactRange: exactRange(),
|
||||
activeIds: activeIds,
|
||||
});
|
||||
|
||||
const seriesNumber = 1 + (top || []).length + (bottom || []).length;
|
||||
|
||||
const charts = [top || [], bottom]
|
||||
.flatMap((list) => (list ? [list] : []))
|
||||
.flatMap((seriesConfigList, index) => {
|
||||
if (index !== 0 && seriesConfigList.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const div = document.createElement("div");
|
||||
|
||||
div.className = "w-full cursor-crosshair min-h-0 border-lighter";
|
||||
|
||||
parentDiv.appendChild(div);
|
||||
|
||||
const chart = createChart(scale, div, {
|
||||
dark,
|
||||
priceScaleOptions: {
|
||||
...priceScaleOptions,
|
||||
},
|
||||
});
|
||||
|
||||
if (!chart) {
|
||||
console.log("chart: undefined");
|
||||
return [];
|
||||
}
|
||||
|
||||
const whitespace = setWhitespace(chart, scale);
|
||||
|
||||
if (exactRange()) {
|
||||
chart.timeScale().setVisibleRange(exactRange());
|
||||
}
|
||||
|
||||
// const whitespace = new Array<ISeriesApi<"Line"> | undefined>(
|
||||
// scale === "date"
|
||||
// ? whitespaceDateDatasets.length
|
||||
// : whitespaceHeightDatasets.length,
|
||||
// ).fill(undefined);
|
||||
|
||||
// function createWhitespaceSeriesIfNeeded(index: number) {
|
||||
// console.log(index);
|
||||
// if (index >= 0 && index < whitespace.length && !whitespace[index]) {
|
||||
// const series = createLineSeries(chart);
|
||||
// whitespace[index] = series;
|
||||
|
||||
// if (scale === "date") {
|
||||
// series.setData(whitespaceDateDatasets[index]);
|
||||
// } else {
|
||||
// series.setData(whitespaceHeightDatasets[index]);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// createEffect(() => {
|
||||
// const ids = activeIds();
|
||||
// console.log(ids);
|
||||
|
||||
// const idsLength = ids.length;
|
||||
// for (let i = 0; i < idsLength; i++) {
|
||||
// const id = ids[i];
|
||||
|
||||
// const whitespaceIndex = chunkIdToIndex(
|
||||
// scale,
|
||||
// scale === "date"
|
||||
// ? id - whitespaceStartDateYear
|
||||
// : id - whitespaceHeightStart,
|
||||
// );
|
||||
|
||||
// if (i === 0) {
|
||||
// createWhitespaceSeriesIfNeeded(whitespaceIndex - 1);
|
||||
// }
|
||||
|
||||
// createWhitespaceSeriesIfNeeded(whitespaceIndex);
|
||||
|
||||
// if (i === idsLength - 1) {
|
||||
// createWhitespaceSeriesIfNeeded(whitespaceIndex + 1);
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
|
||||
const chartLegend: SeriesLegend[] = [];
|
||||
|
||||
const markerCallback = () =>
|
||||
setMinMaxMarkers({
|
||||
scale,
|
||||
visibleRange: exactRange(),
|
||||
legendList: chartLegend,
|
||||
dark,
|
||||
activeIds: activeIds,
|
||||
});
|
||||
|
||||
const debouncedSetMinMaxMarkers = requestIdleCallbackPossible
|
||||
? () => requestIdleCallback(markerCallback)
|
||||
: debounce(
|
||||
markerCallback,
|
||||
seriesNumber * 10 + scale === "date" ? 50 : 100,
|
||||
);
|
||||
|
||||
createEffect(on([exactRange, dark], debouncedSetMinMaxMarkers));
|
||||
|
||||
if (index === 0) {
|
||||
const dataset =
|
||||
priceDataset ||
|
||||
(datasets[preset.scale as Scale].price as unknown as NonNullable<
|
||||
typeof priceDataset
|
||||
>);
|
||||
|
||||
activeDatasets.push(dataset);
|
||||
|
||||
const title = priceOptions?.title || "Price";
|
||||
|
||||
const priceScaleOptions: DeepPartialPriceScaleOptions = {
|
||||
mode: 1,
|
||||
...priceOptions?.priceScaleOptions,
|
||||
};
|
||||
|
||||
function createPriceSeries(seriesType: PriceSeriesType) {
|
||||
let seriesConfig: SeriesConfig<Scale>;
|
||||
|
||||
if (seriesType === "Candlestick") {
|
||||
seriesConfig = {
|
||||
dataset,
|
||||
title,
|
||||
seriesType: SeriesType.Candlestick,
|
||||
options: priceOptions,
|
||||
priceScaleOptions,
|
||||
};
|
||||
} else {
|
||||
seriesConfig = {
|
||||
dataset,
|
||||
title,
|
||||
color: colors.white,
|
||||
options: priceOptions?.seriesOptions,
|
||||
priceScaleOptions,
|
||||
};
|
||||
}
|
||||
|
||||
const priceSeries = createSeriesGroup({
|
||||
index: -1,
|
||||
activeIds,
|
||||
seriesConfig,
|
||||
chart,
|
||||
chartLegend,
|
||||
lastActiveIndex,
|
||||
preset,
|
||||
disabled: () => priceSeriesType() !== seriesType,
|
||||
debouncedSetMinMaxMarkers,
|
||||
dark,
|
||||
});
|
||||
|
||||
createEffect(() => {
|
||||
const latest = webSockets.liveKrakenCandle.latest();
|
||||
|
||||
if (!latest) return;
|
||||
|
||||
const index = chunkIdToIndex(scale, latest.year);
|
||||
|
||||
const series = priceSeries.seriesList.at(index)?.();
|
||||
|
||||
series?.update(latest);
|
||||
});
|
||||
|
||||
return priceSeries;
|
||||
}
|
||||
|
||||
const priceCandlestickLegend = createPriceSeries("Candlestick");
|
||||
const priceLineLegend = createPriceSeries("Line");
|
||||
|
||||
createEffect(() => {
|
||||
priceCandlestickLegend.visible.set(priceLineLegend.visible());
|
||||
});
|
||||
|
||||
createEffect(() => {
|
||||
priceLineLegend.visible.set(priceCandlestickLegend.visible());
|
||||
});
|
||||
}
|
||||
|
||||
seriesConfigList.reverse().forEach((seriesConfig, index) => {
|
||||
activeDatasets.push(seriesConfig.dataset);
|
||||
|
||||
createSeriesGroup({
|
||||
activeIds: activeIds,
|
||||
index,
|
||||
seriesConfig,
|
||||
chartLegend,
|
||||
chart,
|
||||
preset,
|
||||
lastActiveIndex,
|
||||
debouncedSetMinMaxMarkers,
|
||||
dark,
|
||||
});
|
||||
});
|
||||
|
||||
chartLegend.forEach((legend) => {
|
||||
presetLegend.splice(0, 0, legend);
|
||||
|
||||
createEffect(on(legend.visible, debouncedSetMinMaxMarkers));
|
||||
});
|
||||
|
||||
return [
|
||||
{
|
||||
scale,
|
||||
div,
|
||||
chart,
|
||||
whitespace,
|
||||
legendList: chartLegend,
|
||||
debouncedSetMinMaxMarkers,
|
||||
},
|
||||
];
|
||||
}) satisfies ChartObject[];
|
||||
|
||||
createEffect(() => {
|
||||
const visibleCharts: typeof charts = [];
|
||||
|
||||
charts.forEach((chart) => {
|
||||
if (chart.legendList.some((legend) => legend.drawn())) {
|
||||
chart.div.style.border = "";
|
||||
visibleCharts.push(chart);
|
||||
} else {
|
||||
chart.div.style.height = "100%";
|
||||
// chart.div.style.height = "0px";
|
||||
chart.div.style.border = "none";
|
||||
}
|
||||
});
|
||||
|
||||
visibleCharts.forEach(({ div, chart }, index) => {
|
||||
const last = index === visibleCharts.length - 1;
|
||||
|
||||
div.style.height = last ? "100%" : "calc(100% - 62px)";
|
||||
div.style.borderBottomWidth = last ? "none" : "1px";
|
||||
div.style.marginBottom = last ? "" : "-2px";
|
||||
|
||||
chart.timeScale().applyOptions({
|
||||
visible: last,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const debouncedUpdateVisiblePriceSeriesType = debounce(
|
||||
updateVisiblePriceSeriesType,
|
||||
50,
|
||||
);
|
||||
|
||||
initTimeScale({
|
||||
scale,
|
||||
charts,
|
||||
activeIds: activeIds,
|
||||
exactRange,
|
||||
});
|
||||
|
||||
const activeDatasetsLength = activeDatasets.length;
|
||||
createEffect(() => {
|
||||
const range = activeIds();
|
||||
|
||||
untrack(() => {
|
||||
for (let i = 0; i < range.length; i++) {
|
||||
const id = range[i];
|
||||
for (let j = 0; j < activeDatasetsLength; j++) {
|
||||
activeDatasets[j].fetch(id);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const lastChartIndex = charts.length - 1;
|
||||
|
||||
for (let i = 0; i < charts.length; i++) {
|
||||
const chart = charts[i].chart;
|
||||
|
||||
chart.timeScale().subscribeVisibleLogicalRangeChange((timeRange) => {
|
||||
if (!timeRange) return;
|
||||
|
||||
// Must be the chart with the visible timeScale
|
||||
if (i === lastChartIndex) {
|
||||
debouncedUpdateVisiblePriceSeriesType(
|
||||
chart,
|
||||
timeRange,
|
||||
priceSeriesType,
|
||||
);
|
||||
}
|
||||
|
||||
for (let j = 0; j <= lastChartIndex; j++) {
|
||||
if (i !== j) {
|
||||
charts[j].chart.timeScale().setVisibleLogicalRange(timeRange);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
chart.subscribeCrosshairMove(({ time, sourceEvent }) => {
|
||||
// Don't override crosshair position from scroll event
|
||||
if (time && !sourceEvent) return;
|
||||
|
||||
for (let j = 0; j <= lastChartIndex; j++) {
|
||||
const whitespace = charts[j].whitespace;
|
||||
const otherChart = charts[j].chart;
|
||||
|
||||
if (whitespace && i !== j) {
|
||||
if (time) {
|
||||
otherChart.setCrosshairPosition(NaN, time, whitespace);
|
||||
} else {
|
||||
// No time when mouse goes outside the chart
|
||||
otherChart.clearCrosshairPosition();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
legendSetter(presetLegend);
|
||||
|
||||
reactiveChartList.set(() => charts.map(({ chart }) => chart));
|
||||
}
|
||||
|
||||
function updateVisiblePriceSeriesType(
|
||||
chart: IChartApi,
|
||||
range: LogicalRange,
|
||||
priceSeriesType: RWS<PriceSeriesType>,
|
||||
) {
|
||||
try {
|
||||
const width = chart.timeScale().width();
|
||||
|
||||
const ratio = (range.to - range.from) / width;
|
||||
|
||||
if (ratio <= 0.5) {
|
||||
priceSeriesType.set("Candlestick");
|
||||
} else {
|
||||
priceSeriesType.set("Line");
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
|
||||
function createSeriesGroup<Scale extends ResourceScale>({
|
||||
activeIds,
|
||||
seriesConfig,
|
||||
preset,
|
||||
chartLegend,
|
||||
chart,
|
||||
index: seriesIndex,
|
||||
disabled,
|
||||
lastActiveIndex,
|
||||
debouncedSetMinMaxMarkers,
|
||||
dark,
|
||||
}: {
|
||||
activeIds: Accessor<number[]>;
|
||||
seriesConfig: SeriesConfig<Scale>;
|
||||
preset: Preset;
|
||||
chart: IChartApi;
|
||||
index: number;
|
||||
chartLegend: SeriesLegend[];
|
||||
lastActiveIndex: Accessor<number | undefined>;
|
||||
disabled?: Accessor<boolean>;
|
||||
debouncedSetMinMaxMarkers: VoidFunction;
|
||||
dark: Accessor<boolean>;
|
||||
}) {
|
||||
const {
|
||||
dataset,
|
||||
title,
|
||||
colors,
|
||||
color,
|
||||
defaultVisible,
|
||||
seriesType: type,
|
||||
options,
|
||||
priceScaleOptions,
|
||||
} = seriesConfig;
|
||||
|
||||
const scale = preset.scale;
|
||||
|
||||
const seriesList: RWS<
|
||||
ISeriesApi<"Baseline" | "Line" | "Histogram" | "Candlestick"> | undefined
|
||||
>[] = new Array(dataset.fetchedJSONs.length);
|
||||
|
||||
const legend = createSeriesLegend({
|
||||
id: stringToId(title),
|
||||
presetId: preset.id,
|
||||
title,
|
||||
seriesList,
|
||||
color: colors || color || DEFAULT_BASELINE_COLORS,
|
||||
defaultVisible,
|
||||
disabled,
|
||||
dataset,
|
||||
});
|
||||
|
||||
chartLegend.push(legend);
|
||||
|
||||
dataset.fetchedJSONs.forEach((json, index) => {
|
||||
const series: (typeof seriesList)[number] = createRWS(undefined);
|
||||
|
||||
seriesList[index] = series;
|
||||
|
||||
createEffect(() => {
|
||||
const values = json.vec();
|
||||
if (!values) return;
|
||||
|
||||
if (seriesIndex > 0) {
|
||||
let previous = chartLegend.at(seriesIndex - 1)?.seriesList[index];
|
||||
|
||||
if (!previous?.()) return;
|
||||
}
|
||||
|
||||
untrack(() => {
|
||||
let s = series();
|
||||
|
||||
if (!s) {
|
||||
switch (type) {
|
||||
case SeriesType.Based: {
|
||||
s = createBaseLineSeries({
|
||||
chart,
|
||||
dark,
|
||||
color,
|
||||
topColor: seriesConfig.topColor,
|
||||
bottomColor: seriesConfig.bottomColor,
|
||||
options,
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
case SeriesType.Candlestick: {
|
||||
const candlestickSeries = createCandlesticksSeries({
|
||||
chart,
|
||||
options,
|
||||
dark,
|
||||
});
|
||||
|
||||
s = candlestickSeries[0];
|
||||
|
||||
if (!colors && !color) {
|
||||
legend.color = candlestickSeries[1];
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case SeriesType.Histogram: {
|
||||
s = createHistogramSeries({
|
||||
chart,
|
||||
options,
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
case SeriesType.Line: {
|
||||
s = createLineSeries({
|
||||
chart,
|
||||
color,
|
||||
dark,
|
||||
options,
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (priceScaleOptions) {
|
||||
s.priceScale().applyOptions(priceScaleOptions);
|
||||
}
|
||||
|
||||
series.set(s);
|
||||
}
|
||||
|
||||
s.setData(values);
|
||||
|
||||
debouncedSetMinMaxMarkers();
|
||||
});
|
||||
});
|
||||
|
||||
createEffect(() => {
|
||||
const s = series();
|
||||
const currentVec = dataset.fetchedJSONs.at(index)?.vec();
|
||||
const nextVec = dataset.fetchedJSONs.at(index + 1)?.vec();
|
||||
|
||||
if (s && currentVec?.length && nextVec?.length) {
|
||||
s.update(nextVec[0]);
|
||||
}
|
||||
});
|
||||
|
||||
const isLast = createMemo(() => {
|
||||
const last = lastActiveIndex();
|
||||
return last !== undefined && last === index;
|
||||
});
|
||||
|
||||
createEffect(() => {
|
||||
series()?.applyOptions({
|
||||
lastValueVisible: legend.drawn() && isLast(),
|
||||
});
|
||||
});
|
||||
|
||||
const inRange = createMemo(() => {
|
||||
const range = activeIds();
|
||||
|
||||
if (range.length) {
|
||||
const start = chunkIdToIndex(scale, range.at(0)!);
|
||||
const end = chunkIdToIndex(scale, range.at(-1)!);
|
||||
|
||||
if (index >= start && index <= end) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
const visible = createMemo((previous: boolean) => {
|
||||
if (legend.disabled()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return previous || inRange();
|
||||
}, false);
|
||||
|
||||
createEffect(() => {
|
||||
series()?.applyOptions({
|
||||
visible: legend.drawn() && visible(),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return legend;
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { colors } from "../../utils/colors";
|
||||
import { applyMultipleSeries, SeriesType } from "../templates/multiple";
|
||||
import { applySeriesList, SeriesType } from "../apply";
|
||||
|
||||
export function createPresets() {
|
||||
const scale: ResourceScale = "date";
|
||||
@@ -14,12 +14,9 @@ export function createPresets() {
|
||||
title: "Block Height",
|
||||
description: "",
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: "Height",
|
||||
color: colors.bitcoin,
|
||||
@@ -40,19 +37,15 @@ export function createPresets() {
|
||||
title: "Daily Sum Of Blocks Mined",
|
||||
description: "",
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: "Target",
|
||||
color: colors.white,
|
||||
dataset: params.datasets.date.blocks_mined_1d_target,
|
||||
options: {
|
||||
lineStyle: 3,
|
||||
// lineStyle: LineStyle.LargeDashed,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -82,19 +75,15 @@ export function createPresets() {
|
||||
title: "Weekly Sum Of Blocks Mined",
|
||||
description: "",
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: "Target",
|
||||
color: colors.white,
|
||||
dataset: params.datasets.date.blocks_mined_1w_target,
|
||||
options: {
|
||||
lineStyle: 3,
|
||||
// lineStyle: LineStyle.LargeDashed,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -113,18 +102,14 @@ export function createPresets() {
|
||||
title: "Monthly Sum Of Blocks Mined",
|
||||
description: "",
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: "Target",
|
||||
color: colors.white,
|
||||
dataset: params.datasets.date.blocks_mined_1m_target,
|
||||
options: {
|
||||
// lineStyle: LineStyle.LargeDashed,
|
||||
lineStyle: 3,
|
||||
},
|
||||
},
|
||||
@@ -144,19 +129,15 @@ export function createPresets() {
|
||||
title: "Yearly Sum Of Blocks Mined",
|
||||
description: "",
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: "Target",
|
||||
color: colors.white,
|
||||
dataset: params.datasets.date.blocks_mined_1y_target,
|
||||
options: {
|
||||
lineStyle: 3,
|
||||
// lineStyle: LineStyle.LargeDashed,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -175,16 +156,12 @@ export function createPresets() {
|
||||
title: "Total Blocks Mined",
|
||||
description: "",
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: "Mined",
|
||||
color: colors.bitcoin,
|
||||
seriesType: SeriesType.Area,
|
||||
dataset: params.datasets.date.total_blocks_mined,
|
||||
},
|
||||
],
|
||||
@@ -200,16 +177,12 @@ export function createPresets() {
|
||||
title: "Cumulative Block Size",
|
||||
description: "",
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: "Size (MB)",
|
||||
color: colors.darkWhite,
|
||||
seriesType: SeriesType.Area,
|
||||
dataset: params.datasets.date.cumulative_block_size,
|
||||
},
|
||||
],
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { colors } from "../../utils/colors";
|
||||
import { applyMultipleSeries, SeriesType } from "../templates/multiple";
|
||||
import { applySeriesList, SeriesType } from "../apply";
|
||||
|
||||
export function createPresets<Scale extends ResourceScale>({
|
||||
scale,
|
||||
@@ -19,9 +19,9 @@ export function createPresets<Scale extends ResourceScale>({
|
||||
title: "All Cointime Prices",
|
||||
description: "",
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
list: [
|
||||
top: [
|
||||
{
|
||||
title: "Vaulted Price",
|
||||
color: colors.vaultedness,
|
||||
@@ -61,9 +61,9 @@ export function createPresets<Scale extends ResourceScale>({
|
||||
title: "Active Price",
|
||||
description: "",
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
list: [
|
||||
top: [
|
||||
{
|
||||
title: "Active Price",
|
||||
color: colors.liveliness,
|
||||
@@ -85,9 +85,9 @@ export function createPresets<Scale extends ResourceScale>({
|
||||
title: "Vaulted Price",
|
||||
description: "",
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
list: [
|
||||
top: [
|
||||
{
|
||||
title: "Vaulted Price",
|
||||
color: colors.vaultedness,
|
||||
@@ -109,9 +109,9 @@ export function createPresets<Scale extends ResourceScale>({
|
||||
title: "True Market Mean",
|
||||
description: "",
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
list: [
|
||||
top: [
|
||||
{
|
||||
title: "True Market Mean",
|
||||
color: colors.trueMarketMeanPrice,
|
||||
@@ -133,9 +133,9 @@ export function createPresets<Scale extends ResourceScale>({
|
||||
title: "Cointime Price",
|
||||
description: "",
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
list: [
|
||||
top: [
|
||||
{
|
||||
title: "Cointime",
|
||||
color: colors.cointimePrice,
|
||||
@@ -159,16 +159,14 @@ export function createPresets<Scale extends ResourceScale>({
|
||||
title: "Cointime Capitalizations",
|
||||
description: "",
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
mode: 1,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: "Market Cap",
|
||||
|
||||
color: colors.white,
|
||||
dataset: params.datasets[scale].market_cap,
|
||||
},
|
||||
@@ -198,13 +196,12 @@ export function createPresets<Scale extends ResourceScale>({
|
||||
title: "Thermo Cap",
|
||||
description: "",
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
mode: 1,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: "Thermo Cap",
|
||||
color: colors.thermoCap,
|
||||
@@ -221,13 +218,12 @@ export function createPresets<Scale extends ResourceScale>({
|
||||
title: "Investor Cap",
|
||||
description: "",
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
mode: 1,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: "Investor Cap",
|
||||
color: colors.investorCap,
|
||||
@@ -244,12 +240,9 @@ export function createPresets<Scale extends ResourceScale>({
|
||||
title: "Thermo Cap To Investor Cap Ratio (%)",
|
||||
description: "",
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: "Ratio",
|
||||
color: colors.bitcoin,
|
||||
@@ -272,12 +265,9 @@ export function createPresets<Scale extends ResourceScale>({
|
||||
title: "All Coinblocks",
|
||||
description: "",
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: "Coinblocks Created",
|
||||
color: colors.coinblocksCreated,
|
||||
@@ -304,12 +294,9 @@ export function createPresets<Scale extends ResourceScale>({
|
||||
title: "Coinblocks Created",
|
||||
description: "",
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: "Coinblocks Created",
|
||||
color: colors.coinblocksCreated,
|
||||
@@ -326,12 +313,9 @@ export function createPresets<Scale extends ResourceScale>({
|
||||
title: "Coinblocks Destroyed",
|
||||
description: "",
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: "Coinblocks Destroyed",
|
||||
color: colors.coinblocksDestroyed,
|
||||
@@ -348,12 +332,9 @@ export function createPresets<Scale extends ResourceScale>({
|
||||
title: "Coinblocks Stored",
|
||||
description: "",
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: "Coinblocks Stored",
|
||||
color: colors.coinblocksStored,
|
||||
@@ -375,12 +356,9 @@ export function createPresets<Scale extends ResourceScale>({
|
||||
title: "All Cumulative Coinblocks",
|
||||
description: "",
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: "Cumulative Coinblocks Created",
|
||||
color: colors.coinblocksCreated,
|
||||
@@ -410,12 +388,9 @@ export function createPresets<Scale extends ResourceScale>({
|
||||
title: "Cumulative Coinblocks Created",
|
||||
description: "",
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: "Cumulative Coinblocks Created",
|
||||
color: colors.coinblocksCreated,
|
||||
@@ -433,12 +408,9 @@ export function createPresets<Scale extends ResourceScale>({
|
||||
title: "Cumulative Coinblocks Destroyed",
|
||||
description: "",
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: "Cumulative Coinblocks Destroyed",
|
||||
color: colors.coinblocksDestroyed,
|
||||
@@ -456,12 +428,9 @@ export function createPresets<Scale extends ResourceScale>({
|
||||
title: "Cumulative Coinblocks Stored",
|
||||
description: "",
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: "Cumulative Coinblocks Stored",
|
||||
color: colors.coinblocksStored,
|
||||
@@ -484,12 +453,9 @@ export function createPresets<Scale extends ResourceScale>({
|
||||
title: "Liveliness (Activity)",
|
||||
description: "",
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: "Liveliness",
|
||||
color: colors.liveliness,
|
||||
@@ -506,12 +472,9 @@ export function createPresets<Scale extends ResourceScale>({
|
||||
title: "Vaultedness",
|
||||
description: "",
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: "Vaultedness",
|
||||
color: colors.vaultedness,
|
||||
@@ -528,12 +491,9 @@ export function createPresets<Scale extends ResourceScale>({
|
||||
title: "Liveliness V. Vaultedness",
|
||||
description: "",
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: "Liveliness",
|
||||
color: colors.liveliness,
|
||||
@@ -555,12 +515,9 @@ export function createPresets<Scale extends ResourceScale>({
|
||||
title: "Activity To Vaultedness Ratio",
|
||||
description: "",
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: "Activity To Vaultedness Ratio",
|
||||
color: colors.activityToVaultednessRatio,
|
||||
@@ -578,15 +535,12 @@ export function createPresets<Scale extends ResourceScale>({
|
||||
title: "Concurrent Liveliness - Supply Adjusted Coindays Destroyed",
|
||||
description: "",
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: "Concurrent Liveliness 14d Median",
|
||||
color: `${colors.liveliness}66`,
|
||||
color: colors.darkLiveliness,
|
||||
dataset:
|
||||
params.datasets[scale].concurrent_liveliness_2w_median,
|
||||
},
|
||||
@@ -606,12 +560,9 @@ export function createPresets<Scale extends ResourceScale>({
|
||||
title: "Liveliness Incremental Change",
|
||||
description: "",
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: "Liveliness Incremental Change",
|
||||
color: colors.darkLiveliness,
|
||||
@@ -641,12 +592,9 @@ export function createPresets<Scale extends ResourceScale>({
|
||||
title: "Vaulted Supply",
|
||||
description: "",
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: "Vaulted Supply",
|
||||
color: colors.vaultedness,
|
||||
@@ -663,12 +611,9 @@ export function createPresets<Scale extends ResourceScale>({
|
||||
title: "Active Supply",
|
||||
description: "",
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: "Active Supply",
|
||||
color: colors.liveliness,
|
||||
@@ -685,12 +630,9 @@ export function createPresets<Scale extends ResourceScale>({
|
||||
title: "Vaulted V. Active",
|
||||
description: "",
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: "Circulating Supply",
|
||||
color: colors.coinblocksCreated,
|
||||
@@ -747,12 +689,9 @@ export function createPresets<Scale extends ResourceScale>({
|
||||
title: "Vaulted Supply Net Change",
|
||||
description: "",
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: "Vaulted Supply Net Change",
|
||||
color: colors.vaultedness,
|
||||
@@ -769,12 +708,9 @@ export function createPresets<Scale extends ResourceScale>({
|
||||
title: "Active Supply Net Change",
|
||||
description: "",
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: "Active Supply Net Change",
|
||||
color: colors.liveliness,
|
||||
@@ -791,21 +727,18 @@ export function createPresets<Scale extends ResourceScale>({
|
||||
title: "Active VS. Vaulted 90 Day Supply Net Change",
|
||||
description: "",
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: "Active Supply Net Change",
|
||||
color: `${colors.liveliness}80`,
|
||||
color: colors.liveliness,
|
||||
dataset: params.datasets[scale].active_supply_3m_net_change,
|
||||
seriesType: SeriesType.Based,
|
||||
},
|
||||
{
|
||||
title: "Vaulted Supply Net Change",
|
||||
color: `${colors.vaultedPrice}80`,
|
||||
color: colors.vaultedPrice,
|
||||
seriesType: SeriesType.Based,
|
||||
dataset:
|
||||
params.datasets[scale].vaulted_supply_3m_net_change,
|
||||
@@ -919,12 +852,9 @@ export function createPresets<Scale extends ResourceScale>({
|
||||
title: "Cointime Supply In Profit",
|
||||
description: "",
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: "Circulating Supply",
|
||||
color: colors.coinblocksCreated,
|
||||
@@ -951,12 +881,9 @@ export function createPresets<Scale extends ResourceScale>({
|
||||
title: "Cointime Supply In Loss",
|
||||
description: "",
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: "Circulating Supply",
|
||||
color: colors.coinblocksCreated,
|
||||
@@ -985,13 +912,12 @@ export function createPresets<Scale extends ResourceScale>({
|
||||
title: "Cointime-Adjusted Yearly Inflation Rate (%)",
|
||||
description: "",
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
mode: 1,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: "Cointime Adjusted",
|
||||
color: colors.coinblocksCreated,
|
||||
@@ -1015,13 +941,12 @@ export function createPresets<Scale extends ResourceScale>({
|
||||
title: "Cointime-Adjusted Transactions Velocity",
|
||||
description: "",
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
mode: 1,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: "Cointime Adjusted",
|
||||
color: colors.coinblocksCreated,
|
||||
|
||||
@@ -6,8 +6,8 @@ import {
|
||||
yearCohorts,
|
||||
} from "../../datasets/consts/age";
|
||||
import { colors } from "../../utils/colors";
|
||||
import { applySeriesList } from "../apply";
|
||||
import { createCohortPresetFolder } from "../templates/cohort";
|
||||
import { applyMultipleSeries } from "../templates/multiple";
|
||||
|
||||
export function createPresets({ scale }: { scale: ResourceScale }) {
|
||||
return {
|
||||
@@ -20,12 +20,9 @@ export function createPresets({ scale }: { scale: ResourceScale }) {
|
||||
description: "",
|
||||
icon: IconTablerRipple,
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: `24h`,
|
||||
color: colors.up_to_1d,
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
import { phone } from "/src/env";
|
||||
import { createRWS } from "/src/solid/rws";
|
||||
|
||||
import { colors } from "../utils/colors";
|
||||
import { replaceHistory } from "../utils/history";
|
||||
import { stringToId } from "../utils/id";
|
||||
import { resetURLParams } from "../utils/urlParams";
|
||||
import { createPresets as createAddressesPresets } from "./addresses";
|
||||
import {
|
||||
createPresets as createAddressesPresets,
|
||||
createLiquidityFolder,
|
||||
} from "./addresses";
|
||||
import { createPresets as createBlocksPresets } from "./blocks";
|
||||
import { createPresets as createCoinblocksPresets } from "./coinblocks";
|
||||
import { createPresets as createHodlersPresets } from "./hodlers";
|
||||
@@ -29,9 +33,9 @@ export function createPresets(): Presets {
|
||||
name: "Charts",
|
||||
tree: [
|
||||
{
|
||||
name: "By Date",
|
||||
name: "By Block Date",
|
||||
tree: [
|
||||
createMarketPresets({ scale: "date" }),
|
||||
createMarketPresets("date"),
|
||||
createBlocksPresets(),
|
||||
createMinersPresets("date"),
|
||||
createTransactionsPresets("date"),
|
||||
@@ -42,29 +46,42 @@ export function createPresets(): Presets {
|
||||
name: "",
|
||||
title: "",
|
||||
}),
|
||||
createLiquidityFolder({
|
||||
scale: "date",
|
||||
color: colors.bitcoin,
|
||||
datasetKey: "",
|
||||
name: "",
|
||||
}),
|
||||
createHodlersPresets({ scale: "date" }),
|
||||
createAddressesPresets({ scale: "date" }),
|
||||
createCoinblocksPresets({ scale: "date" }),
|
||||
],
|
||||
} satisfies PartialPresetFolder,
|
||||
{
|
||||
name: "By Height (Coming soon)",
|
||||
tree: [
|
||||
// createMarketPresets({ scale: "height", datasets }),
|
||||
// createMinersPresets("height"),
|
||||
// createTransactionsPresets("height"),
|
||||
// ...createCohortPresetList({
|
||||
// datasets,
|
||||
// scale: "height",
|
||||
// color: colors.bitcoin,
|
||||
// name: "",
|
||||
// datasetKey: "",
|
||||
// title: "",
|
||||
// }),
|
||||
// createHodlersPresets({ scale: "height", datasets }),
|
||||
// createAddressesPresets({ scale: "height", datasets }),
|
||||
// createCoinblocksPresets({ scale: "height", datasets }),
|
||||
],
|
||||
name: "By Block Height - Desktop/Tablet Only",
|
||||
tree: !phone
|
||||
? [
|
||||
createMarketPresets("height"),
|
||||
createMinersPresets("height"),
|
||||
createTransactionsPresets("height"),
|
||||
...createCohortPresetList({
|
||||
scale: "height",
|
||||
color: colors.bitcoin,
|
||||
name: "",
|
||||
datasetKey: "",
|
||||
title: "",
|
||||
}),
|
||||
createLiquidityFolder({
|
||||
scale: "height",
|
||||
color: colors.bitcoin,
|
||||
datasetKey: "",
|
||||
name: "",
|
||||
}),
|
||||
createHodlersPresets({ scale: "height" }),
|
||||
createAddressesPresets({ scale: "height" }),
|
||||
createCoinblocksPresets({ scale: "height" }),
|
||||
]
|
||||
: [],
|
||||
} satisfies PartialPresetFolder,
|
||||
],
|
||||
},
|
||||
@@ -102,7 +119,7 @@ export function createPresets(): Presets {
|
||||
const serializedHistory: SerializedPresetsHistory = history().map(
|
||||
({ preset, date }) => ({
|
||||
p: preset.id,
|
||||
d: date.valueOf(),
|
||||
d: date.getTime(),
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -249,7 +266,14 @@ function checkIfDuplicateIds(ids: string[]) {
|
||||
}
|
||||
|
||||
function findInitialPreset(presets: Preset[]): Preset {
|
||||
const urlPreset = document.location.pathname.substring(1);
|
||||
let urlPreset = document.location.pathname.substring(1);
|
||||
|
||||
if (phone && urlPreset.startsWith("height" satisfies ResourceScale)) {
|
||||
urlPreset = urlPreset.replace(
|
||||
"height" satisfies ResourceScale,
|
||||
"date" satisfies ResourceScale,
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
(urlPreset &&
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { averages } from "/src/scripts/datasets/date";
|
||||
import { colors } from "/src/scripts/utils/colors";
|
||||
|
||||
import { applyMultipleSeries } from "../../templates/multiple";
|
||||
import { applySeriesList } from "../../apply";
|
||||
|
||||
export function createPresets(): PartialPresetFolder {
|
||||
const scale: ResourceScale = "date";
|
||||
@@ -15,9 +15,9 @@ export function createPresets(): PartialPresetFolder {
|
||||
name: "All",
|
||||
title: "All Averages",
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
list: averages.map((average) => ({
|
||||
top: averages.map((average) => ({
|
||||
title: average.key.toUpperCase(),
|
||||
color: colors[`_${average.key}`],
|
||||
dataset: params.datasets.date[`price_${average.key}_sma`],
|
||||
@@ -45,24 +45,20 @@ function createPresetFolder({
|
||||
key,
|
||||
}: {
|
||||
scale: ResourceScale;
|
||||
color: string;
|
||||
color: Color;
|
||||
name: string;
|
||||
key: AverageName;
|
||||
}) {
|
||||
return {
|
||||
// id,
|
||||
// name,
|
||||
// tree: [
|
||||
// {
|
||||
scale,
|
||||
name,
|
||||
description: "",
|
||||
icon: IconTablerMathAvg,
|
||||
title: `${name} Moving Average`,
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
list: [
|
||||
top: [
|
||||
{
|
||||
title: `SMA`,
|
||||
color,
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { colors } from "../../utils/colors";
|
||||
import { applyMultipleSeries } from "../templates/multiple";
|
||||
import { applySeriesList } from "../apply";
|
||||
import { createPresets as createAveragesPresets } from "./averages";
|
||||
import { createPresets as createIndicatorsPresets } from "./indicators";
|
||||
import { createPresets as createReturnsPresets } from "./returns";
|
||||
|
||||
export function createPresets({ scale }: { scale: ResourceScale }) {
|
||||
export function createPresets(scale: ResourceScale) {
|
||||
return {
|
||||
name: "Market",
|
||||
tree: [
|
||||
@@ -14,26 +14,7 @@ export function createPresets({ scale }: { scale: ResourceScale }) {
|
||||
name: "Price",
|
||||
title: "Market Price",
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({ ...params });
|
||||
},
|
||||
description: "",
|
||||
},
|
||||
{
|
||||
scale,
|
||||
icon: IconTablerPercentage,
|
||||
name: "Performance",
|
||||
title: "Market Performance",
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
...params,
|
||||
priceOptions: {
|
||||
id: "performance",
|
||||
title: "Performance",
|
||||
priceScaleOptions: {
|
||||
mode: 2,
|
||||
},
|
||||
},
|
||||
});
|
||||
return applySeriesList(params);
|
||||
},
|
||||
description: "",
|
||||
},
|
||||
@@ -43,12 +24,9 @@ export function createPresets({ scale }: { scale: ResourceScale }) {
|
||||
name: "Capitalization",
|
||||
title: "Market Capitalization",
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: "Market Cap.",
|
||||
dataset: params.datasets[scale].market_cap,
|
||||
|
||||
@@ -3,7 +3,7 @@ import {
|
||||
totalReturns,
|
||||
} from "/src/scripts/datasets/consts/returns";
|
||||
|
||||
import { applyMultipleSeries, SeriesType } from "../../templates/multiple";
|
||||
import { applySeriesList, SeriesType } from "../../apply";
|
||||
|
||||
export function createPresets() {
|
||||
return {
|
||||
@@ -57,12 +57,9 @@ function createPreset({
|
||||
icon: IconTablerReceiptTax,
|
||||
title: `${title} Return`,
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: `Return (%)`,
|
||||
seriesType: SeriesType.Based,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { colors } from "../../utils/colors";
|
||||
import { applyMultipleSeries, SeriesType } from "../templates/multiple";
|
||||
import { applySeriesList, SeriesType } from "../apply";
|
||||
|
||||
export function createPresets(scale: ResourceScale) {
|
||||
return {
|
||||
@@ -20,12 +20,9 @@ export function createPresets(scale: ResourceScale) {
|
||||
title: "Last Coinbase (In Bitcoin)",
|
||||
description: "",
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: "Last",
|
||||
color: colors.bitcoin,
|
||||
@@ -42,12 +39,9 @@ export function createPresets(scale: ResourceScale) {
|
||||
title: "Last Coinbase (In Dollars)",
|
||||
description: "",
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: "Last",
|
||||
color: colors.dollars,
|
||||
@@ -71,12 +65,9 @@ export function createPresets(scale: ResourceScale) {
|
||||
title: "Daily Sum Of Bitcoin Coinbases",
|
||||
description: "",
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: "Coinbases (Bitcoin)",
|
||||
color: colors.bitcoin,
|
||||
@@ -93,12 +84,9 @@ export function createPresets(scale: ResourceScale) {
|
||||
title: "Daily Sum Of Dollar Coinbases",
|
||||
description: "",
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: "Coinbases (Dollars)",
|
||||
color: colors.dollars,
|
||||
@@ -122,12 +110,9 @@ export function createPresets(scale: ResourceScale) {
|
||||
title: "Yearly Sum Of Bitcoin Coinbases",
|
||||
description: "",
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: "Coinbases (Bitcoin)",
|
||||
color: colors.bitcoin,
|
||||
@@ -144,12 +129,9 @@ export function createPresets(scale: ResourceScale) {
|
||||
title: "Yearly Sum Of Dollar Coinbases",
|
||||
description: "",
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: "Coinbases (Dollars)",
|
||||
color: colors.dollars,
|
||||
@@ -174,12 +156,9 @@ export function createPresets(scale: ResourceScale) {
|
||||
title: "Cumulative Bitcoin Coinbases",
|
||||
description: "",
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: "Coinbases (Bitcoin)",
|
||||
color: colors.bitcoin,
|
||||
@@ -197,12 +176,9 @@ export function createPresets(scale: ResourceScale) {
|
||||
title: "Cumulative Dollar Coinbases",
|
||||
description: "",
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: "Coinbases (Dollars)",
|
||||
color: colors.dollars,
|
||||
@@ -236,12 +212,10 @@ export function createPresets(scale: ResourceScale) {
|
||||
title: "Last Subsidy (In Bitcoin)",
|
||||
description: "",
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
},
|
||||
list: [
|
||||
|
||||
bottom: [
|
||||
{
|
||||
title: "Last",
|
||||
color: colors.bitcoin,
|
||||
@@ -258,12 +232,9 @@ export function createPresets(scale: ResourceScale) {
|
||||
title: "Last Subsidy (In Dollars)",
|
||||
description: "",
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: "Last",
|
||||
color: colors.dollars,
|
||||
@@ -287,12 +258,9 @@ export function createPresets(scale: ResourceScale) {
|
||||
title: "Daily Sum Of Bitcoin Subsidies",
|
||||
description: "",
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: "Subsidies (Bitcoin)",
|
||||
color: colors.bitcoin,
|
||||
@@ -309,12 +277,9 @@ export function createPresets(scale: ResourceScale) {
|
||||
title: "Daily Sum Of Dollar Subsidies",
|
||||
description: "",
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: "Subsidies (Dollars)",
|
||||
color: colors.dollars,
|
||||
@@ -338,12 +303,9 @@ export function createPresets(scale: ResourceScale) {
|
||||
title: "Yearly Sum Of Bitcoin Subsidies",
|
||||
description: "",
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: "Subsidies (Bitcoin)",
|
||||
color: colors.bitcoin,
|
||||
@@ -360,12 +322,9 @@ export function createPresets(scale: ResourceScale) {
|
||||
title: "Yearly Sum Of Dollar Subsidies",
|
||||
description: "",
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: "Subsidies (Dollars)",
|
||||
color: colors.dollars,
|
||||
@@ -390,12 +349,9 @@ export function createPresets(scale: ResourceScale) {
|
||||
title: "Cumulative Bitcoin Subsidies",
|
||||
description: "",
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: "Subsidies (Bitcoin)",
|
||||
color: colors.bitcoin,
|
||||
@@ -413,12 +369,9 @@ export function createPresets(scale: ResourceScale) {
|
||||
title: "Cumulative Dollar Subsidies",
|
||||
description: "",
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: "Subsidies (Dollars)",
|
||||
color: colors.dollars,
|
||||
@@ -452,12 +405,9 @@ export function createPresets(scale: ResourceScale) {
|
||||
title: "Last Fees (In Bitcoin)",
|
||||
description: "",
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: "Last",
|
||||
color: colors.bitcoin,
|
||||
@@ -474,12 +424,9 @@ export function createPresets(scale: ResourceScale) {
|
||||
title: "Last Fees (In Dollars)",
|
||||
description: "",
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: "Last",
|
||||
color: colors.dollars,
|
||||
@@ -503,12 +450,9 @@ export function createPresets(scale: ResourceScale) {
|
||||
title: "Daily Sum Of Bitcoin Fees",
|
||||
description: "",
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: "Fees (Bitcoin)",
|
||||
color: colors.bitcoin,
|
||||
@@ -525,12 +469,9 @@ export function createPresets(scale: ResourceScale) {
|
||||
title: "Daily Sum Of Dollar Fees",
|
||||
description: "",
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: "Fees (Dollars)",
|
||||
color: colors.dollars,
|
||||
@@ -553,12 +494,9 @@ export function createPresets(scale: ResourceScale) {
|
||||
title: "Yearly Sum Of Bitcoin Fees",
|
||||
description: "",
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: "Fees (Bitcoin)",
|
||||
color: colors.bitcoin,
|
||||
@@ -575,12 +513,9 @@ export function createPresets(scale: ResourceScale) {
|
||||
title: "Yearly Sum Of Dollar Fees",
|
||||
description: "",
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: "Fees (Dollars)",
|
||||
color: colors.dollars,
|
||||
@@ -604,12 +539,9 @@ export function createPresets(scale: ResourceScale) {
|
||||
title: "Cumulative Bitcoin Fees",
|
||||
description: "",
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: "Fees (Bitcoin)",
|
||||
color: colors.bitcoin,
|
||||
@@ -626,12 +558,9 @@ export function createPresets(scale: ResourceScale) {
|
||||
title: "Cumulative Dollar Fees",
|
||||
description: "",
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: "Fees (Dollars)",
|
||||
color: colors.dollars,
|
||||
@@ -657,12 +586,9 @@ export function createPresets(scale: ResourceScale) {
|
||||
title: "Subsidy V. Fees",
|
||||
description: "",
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: "Subsidy (%)",
|
||||
color: colors.bitcoin,
|
||||
@@ -687,13 +613,12 @@ export function createPresets(scale: ResourceScale) {
|
||||
title: "Puell Multiple",
|
||||
description: "",
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
mode: 1,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: "Multiple",
|
||||
color: colors.bitcoin,
|
||||
@@ -711,13 +636,12 @@ export function createPresets(scale: ResourceScale) {
|
||||
title: "Hash Rate (EH/s)",
|
||||
description: "",
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
mode: 1,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: "1M SMA",
|
||||
color: colors.momentumYellow,
|
||||
@@ -745,13 +669,12 @@ export function createPresets(scale: ResourceScale) {
|
||||
title: "Hash Ribbon (EH/s)",
|
||||
description: "",
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
mode: 1,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: "1M SMA",
|
||||
color: colors.profit,
|
||||
@@ -774,12 +697,9 @@ export function createPresets(scale: ResourceScale) {
|
||||
title: "Hash Price",
|
||||
description: "",
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: "Price ($/PH/s)",
|
||||
color: colors.dollars,
|
||||
@@ -799,13 +719,12 @@ export function createPresets(scale: ResourceScale) {
|
||||
title: "Difficulty",
|
||||
description: "",
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
mode: 1,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: "Difficulty",
|
||||
color: colors.bitcoin,
|
||||
@@ -825,15 +744,11 @@ export function createPresets(scale: ResourceScale) {
|
||||
title: "Difficulty Adjustment",
|
||||
description: "",
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: "Adjustment (%)",
|
||||
// color: colors.bitcoin,
|
||||
seriesType: SeriesType.Based,
|
||||
dataset: params.datasets[scale].difficulty_adjustment,
|
||||
},
|
||||
@@ -851,13 +766,12 @@ export function createPresets(scale: ResourceScale) {
|
||||
title: "Annualized Issuance",
|
||||
description: "",
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
mode: 1,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: "Issuance",
|
||||
color: colors.bitcoin,
|
||||
@@ -875,13 +789,12 @@ export function createPresets(scale: ResourceScale) {
|
||||
title: "Yearly Inflation Rate",
|
||||
description: "",
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
mode: 1,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: "Rate (%)",
|
||||
color: colors.bitcoin,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { percentiles } from "../../datasets/consts/percentiles";
|
||||
import { colors } from "../../utils/colors";
|
||||
import { applyMultipleSeries, SeriesType } from "./multiple";
|
||||
import { applySeriesList, SeriesType } from "../apply";
|
||||
|
||||
export function createCohortPresetFolder<Scale extends ResourceScale>({
|
||||
scale,
|
||||
@@ -12,7 +12,7 @@ export function createCohortPresetFolder<Scale extends ResourceScale>({
|
||||
scale: Scale;
|
||||
name: string;
|
||||
datasetKey: AnyPossibleCohortKey;
|
||||
color: string;
|
||||
color: Color;
|
||||
title: string;
|
||||
}) {
|
||||
return {
|
||||
@@ -38,7 +38,7 @@ export function createCohortPresetList<Scale extends ResourceScale>({
|
||||
scale: Scale;
|
||||
datasetKey: AnyPossibleCohortKey;
|
||||
title: string;
|
||||
color: string;
|
||||
color: Color;
|
||||
}) {
|
||||
const datasetPrefix = datasetKey
|
||||
? (`${datasetKey}_` as const)
|
||||
@@ -54,16 +54,12 @@ export function createCohortPresetList<Scale extends ResourceScale>({
|
||||
title: `${title} Unspent Transaction Outputs Count`,
|
||||
icon: () => IconTablerTicket,
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: "Count",
|
||||
color,
|
||||
seriesType: SeriesType.Area,
|
||||
dataset: params.datasets[scale][`${datasetPrefix}utxo_count`],
|
||||
},
|
||||
],
|
||||
@@ -83,9 +79,9 @@ export function createCohortPresetList<Scale extends ResourceScale>({
|
||||
description: "",
|
||||
icon: () => IconTablerTag,
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
list: [
|
||||
top: [
|
||||
{
|
||||
title: "Realized Price",
|
||||
color,
|
||||
@@ -102,16 +98,12 @@ export function createCohortPresetList<Scale extends ResourceScale>({
|
||||
title: `${title} Realized Capitalization`,
|
||||
icon: () => IconTablerPigMoney,
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: `${name} Realized Cap.`,
|
||||
color,
|
||||
seriesType: SeriesType.Area,
|
||||
dataset:
|
||||
params.datasets[scale][`${datasetPrefix}realized_cap`],
|
||||
},
|
||||
@@ -136,12 +128,9 @@ export function createCohortPresetList<Scale extends ResourceScale>({
|
||||
title: `${title} Realized Capitalization 1 Month Net Change`,
|
||||
icon: () => IconTablerStatusChange,
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: `Net Change`,
|
||||
seriesType: SeriesType.Based,
|
||||
@@ -161,18 +150,14 @@ export function createCohortPresetList<Scale extends ResourceScale>({
|
||||
title: `${title} Realized Profit`,
|
||||
icon: () => IconTablerCash,
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: "Realized Profit",
|
||||
dataset:
|
||||
params.datasets[scale][`${datasetPrefix}realized_profit`],
|
||||
color: colors.profit,
|
||||
seriesType: SeriesType.Area,
|
||||
},
|
||||
],
|
||||
});
|
||||
@@ -185,18 +170,14 @@ export function createCohortPresetList<Scale extends ResourceScale>({
|
||||
title: `${title} Realized Loss`,
|
||||
icon: () => IconTablerCoffin,
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: "Realized Loss",
|
||||
dataset:
|
||||
params.datasets[scale][`${datasetPrefix}realized_loss`],
|
||||
color: colors.loss,
|
||||
seriesType: SeriesType.Area,
|
||||
},
|
||||
],
|
||||
});
|
||||
@@ -209,12 +190,9 @@ export function createCohortPresetList<Scale extends ResourceScale>({
|
||||
title: `${title} Realized Profit And Loss`,
|
||||
icon: () => IconTablerArrowsVertical,
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: "Profit",
|
||||
color: colors.profit,
|
||||
@@ -242,12 +220,9 @@ export function createCohortPresetList<Scale extends ResourceScale>({
|
||||
title: `${title} Net Realized Profit And Loss`,
|
||||
icon: () => IconTablerScale,
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: "Net PNL",
|
||||
seriesType: SeriesType.Based,
|
||||
@@ -267,12 +242,9 @@ export function createCohortPresetList<Scale extends ResourceScale>({
|
||||
title: `${title} Net Realized Profit And Loss Relative To Market Capitalization`,
|
||||
icon: () => IconTablerDivide,
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: "Net",
|
||||
seriesType: SeriesType.Based,
|
||||
@@ -292,16 +264,12 @@ export function createCohortPresetList<Scale extends ResourceScale>({
|
||||
title: `${title} Cumulative Realized Profit`,
|
||||
icon: () => IconTablerSum,
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: "Cumulative Realized Profit",
|
||||
color: colors.profit,
|
||||
seriesType: SeriesType.Area,
|
||||
dataset:
|
||||
params.datasets[scale][
|
||||
`${datasetPrefix}cumulative_realized_profit`
|
||||
@@ -318,16 +286,12 @@ export function createCohortPresetList<Scale extends ResourceScale>({
|
||||
title: `${title} Cumulative Realized Loss`,
|
||||
icon: () => IconTablerSum,
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: "Cumulative Realized Loss",
|
||||
color: colors.loss,
|
||||
seriesType: SeriesType.Area,
|
||||
dataset:
|
||||
params.datasets[scale][
|
||||
`${datasetPrefix}cumulative_realized_loss`
|
||||
@@ -344,12 +308,9 @@ export function createCohortPresetList<Scale extends ResourceScale>({
|
||||
title: `${title} Cumulative Net Realized Profit And Loss`,
|
||||
icon: () => IconTablerSum,
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: "Cumulative Net Realized PNL",
|
||||
seriesType: SeriesType.Based,
|
||||
@@ -369,12 +330,9 @@ export function createCohortPresetList<Scale extends ResourceScale>({
|
||||
title: `${title} Cumulative Net Realized Profit And Loss 30 Day Change`,
|
||||
icon: () => IconTablerTimeDuration30,
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: "Cumulative Net Realized PNL 30d Change",
|
||||
dataset:
|
||||
@@ -399,18 +357,14 @@ export function createCohortPresetList<Scale extends ResourceScale>({
|
||||
title: `${title} Unrealized Profit`,
|
||||
icon: () => IconTablerMoodDollar,
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: "Profit",
|
||||
dataset:
|
||||
params.datasets[scale][`${datasetPrefix}unrealized_profit`],
|
||||
color: colors.profit,
|
||||
seriesType: SeriesType.Area,
|
||||
},
|
||||
],
|
||||
});
|
||||
@@ -424,18 +378,14 @@ export function createCohortPresetList<Scale extends ResourceScale>({
|
||||
title: `${title} Unrealized Loss`,
|
||||
icon: () => IconTablerMoodSadDizzy,
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: "Loss",
|
||||
dataset:
|
||||
params.datasets[scale][`${datasetPrefix}unrealized_loss`],
|
||||
color: colors.loss,
|
||||
seriesType: SeriesType.Area,
|
||||
},
|
||||
],
|
||||
});
|
||||
@@ -448,12 +398,9 @@ export function createCohortPresetList<Scale extends ResourceScale>({
|
||||
title: `${title} Unrealized Profit And Loss`,
|
||||
icon: () => IconTablerArrowsVertical,
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: "Profit",
|
||||
color: colors.profit,
|
||||
@@ -481,12 +428,9 @@ export function createCohortPresetList<Scale extends ResourceScale>({
|
||||
title: `${title} Net Unrealized Profit And Loss`,
|
||||
icon: () => IconTablerScale,
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: "Net Unrealized PNL",
|
||||
dataset:
|
||||
@@ -506,12 +450,9 @@ export function createCohortPresetList<Scale extends ResourceScale>({
|
||||
title: `${title} Net Unrealized Profit And Loss Relative To Total Market Capitalization`,
|
||||
icon: () => IconTablerDivide,
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: "Relative Net Unrealized PNL",
|
||||
dataset:
|
||||
@@ -540,12 +481,9 @@ export function createCohortPresetList<Scale extends ResourceScale>({
|
||||
icon: () => IconTablerArrowsCross,
|
||||
description: "",
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: "In Profit",
|
||||
color: colors.profit,
|
||||
@@ -587,16 +525,12 @@ export function createCohortPresetList<Scale extends ResourceScale>({
|
||||
icon: () => IconTablerSum,
|
||||
description: "",
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: "Supply",
|
||||
color,
|
||||
seriesType: SeriesType.Area,
|
||||
dataset: params.datasets[scale][`${datasetPrefix}supply`],
|
||||
},
|
||||
],
|
||||
@@ -609,16 +543,12 @@ export function createCohortPresetList<Scale extends ResourceScale>({
|
||||
title: `${title} Supply In Profit`,
|
||||
icon: () => IconTablerTrendingUp,
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: "Supply",
|
||||
color: colors.profit,
|
||||
seriesType: SeriesType.Area,
|
||||
dataset:
|
||||
params.datasets[scale][
|
||||
`${datasetPrefix}supply_in_profit`
|
||||
@@ -635,16 +565,12 @@ export function createCohortPresetList<Scale extends ResourceScale>({
|
||||
title: `${title} Supply In Loss`,
|
||||
icon: () => IconTablerTrendingDown,
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: "Supply",
|
||||
color: colors.loss,
|
||||
seriesType: SeriesType.Area,
|
||||
dataset:
|
||||
params.datasets[scale][
|
||||
`${datasetPrefix}supply_in_loss`
|
||||
@@ -667,12 +593,9 @@ export function createCohortPresetList<Scale extends ResourceScale>({
|
||||
icon: () => IconTablerArrowsCross,
|
||||
description: "",
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: "In Profit",
|
||||
color: colors.profit,
|
||||
@@ -718,16 +641,12 @@ export function createCohortPresetList<Scale extends ResourceScale>({
|
||||
title: `${title} Total supply Relative To Circulating Supply`,
|
||||
icon: () => IconTablerSum,
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: "Supply",
|
||||
color,
|
||||
seriesType: SeriesType.Area,
|
||||
dataset:
|
||||
params.datasets[scale][
|
||||
`${datasetPrefix}supply_to_circulating_supply_ratio`
|
||||
@@ -744,16 +663,12 @@ export function createCohortPresetList<Scale extends ResourceScale>({
|
||||
title: `${title} Supply In Profit Relative To Circulating Supply`,
|
||||
icon: () => IconTablerTrendingUp,
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: "Supply",
|
||||
color: colors.profit,
|
||||
seriesType: SeriesType.Area,
|
||||
dataset:
|
||||
params.datasets[scale][
|
||||
`${datasetPrefix}supply_in_profit_to_circulating_supply_ratio`
|
||||
@@ -770,15 +685,11 @@ export function createCohortPresetList<Scale extends ResourceScale>({
|
||||
title: `${title} Supply In Loss Relative To Circulating Supply`,
|
||||
icon: () => IconTablerTrendingDown,
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: "Supply",
|
||||
seriesType: SeriesType.Area,
|
||||
color: colors.loss,
|
||||
dataset:
|
||||
params.datasets[scale][
|
||||
@@ -801,14 +712,11 @@ export function createCohortPresetList<Scale extends ResourceScale>({
|
||||
title: `${title} Supply In Profit And Loss Relative To Own Supply`,
|
||||
icon: () => IconTablerArrowsCross,
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: "In profit",
|
||||
title: "In Profit",
|
||||
dataset:
|
||||
params.datasets[scale][
|
||||
`${datasetPrefix}supply_in_profit_to_own_supply_ratio`
|
||||
@@ -816,7 +724,7 @@ export function createCohortPresetList<Scale extends ResourceScale>({
|
||||
color: colors.profit,
|
||||
},
|
||||
{
|
||||
title: "In loss",
|
||||
title: "In Loss",
|
||||
color: colors.loss,
|
||||
dataset:
|
||||
params.datasets[scale][
|
||||
@@ -851,16 +759,12 @@ export function createCohortPresetList<Scale extends ResourceScale>({
|
||||
title: `${title} Supply In Profit Relative To Own Supply`,
|
||||
icon: () => IconTablerTrendingUp,
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: "Supply",
|
||||
color: colors.profit,
|
||||
seriesType: SeriesType.Area,
|
||||
dataset:
|
||||
params.datasets[scale][
|
||||
`${datasetPrefix}supply_in_profit_to_own_supply_ratio`
|
||||
@@ -877,15 +781,11 @@ export function createCohortPresetList<Scale extends ResourceScale>({
|
||||
title: `${title} Supply In Loss Relative To Own Supply`,
|
||||
icon: () => IconTablerTrendingDown,
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: "Supply",
|
||||
seriesType: SeriesType.Area,
|
||||
color: colors.loss,
|
||||
dataset:
|
||||
params.datasets[scale][
|
||||
@@ -917,9 +817,9 @@ export function createCohortPresetList<Scale extends ResourceScale>({
|
||||
title: `${title} Average Price Paid - Realized Price`,
|
||||
icon: () => IconTablerMathAvg,
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
list: [
|
||||
top: [
|
||||
{
|
||||
title: "Average",
|
||||
color,
|
||||
@@ -937,9 +837,9 @@ export function createCohortPresetList<Scale extends ResourceScale>({
|
||||
title: `${title} deciles`,
|
||||
icon: () => IconTablerSquareHalf,
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
list: percentiles
|
||||
top: percentiles
|
||||
.filter(({ value }) => Number(value) % 10 === 0)
|
||||
.map(({ name, key }) => ({
|
||||
dataset: params.datasets[scale][`${datasetPrefix}${key}`],
|
||||
@@ -957,9 +857,9 @@ export function createCohortPresetList<Scale extends ResourceScale>({
|
||||
title: `${title} ${percentile.title}`,
|
||||
icon: () => IconTablerSquareHalf,
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
list: [
|
||||
top: [
|
||||
{
|
||||
title: percentile.name,
|
||||
color,
|
||||
|
||||
@@ -1,183 +0,0 @@
|
||||
import { applyPriceSeries } from "../../lightweightCharts/chart/price";
|
||||
import { chartState } from "../../lightweightCharts/chart/state";
|
||||
import { setTimeScale } from "../../lightweightCharts/chart/time";
|
||||
import { createAreaSeries } from "../../lightweightCharts/series/creators/area";
|
||||
import {
|
||||
createBaseLineSeries,
|
||||
DEFAULT_BASELINE_COLORS,
|
||||
} from "../../lightweightCharts/series/creators/baseLine";
|
||||
import { createHistogramSeries } from "../../lightweightCharts/series/creators/histogram";
|
||||
import { createSeriesLegend } from "../../lightweightCharts/series/creators/legend";
|
||||
import { createLineSeries } from "../../lightweightCharts/series/creators/line";
|
||||
import { resetRightPriceScale } from "../../lightweightCharts/series/options/priceScale";
|
||||
import { stringToId } from "../../utils/id";
|
||||
|
||||
export enum SeriesType {
|
||||
Normal,
|
||||
Based,
|
||||
Area,
|
||||
Histogram,
|
||||
}
|
||||
|
||||
export function applyMultipleSeries<
|
||||
Scale extends ResourceScale,
|
||||
DS extends Dataset<Scale> & Partial<ResourceDataset<Scale>>,
|
||||
>({
|
||||
chart,
|
||||
list = [],
|
||||
preset,
|
||||
priceScaleOptions,
|
||||
datasets,
|
||||
priceDataset,
|
||||
priceOptions,
|
||||
activeResources,
|
||||
}: {
|
||||
chart: IChartApi;
|
||||
preset: Preset;
|
||||
priceDataset?: DS;
|
||||
priceOptions?: PriceSeriesOptions;
|
||||
priceScaleOptions?: FullPriceScaleOptions;
|
||||
list?: (
|
||||
| {
|
||||
dataset: DS;
|
||||
color?: string;
|
||||
colors?: undefined;
|
||||
seriesType: SeriesType.Based;
|
||||
title: string;
|
||||
options?: BaselineSeriesOptions;
|
||||
defaultVisible?: boolean;
|
||||
}
|
||||
| {
|
||||
dataset: DS;
|
||||
color?: string;
|
||||
colors?: string[];
|
||||
seriesType: SeriesType.Histogram;
|
||||
title: string;
|
||||
options?: DeepPartialHistogramOptions;
|
||||
defaultVisible?: boolean;
|
||||
}
|
||||
| {
|
||||
dataset: DS;
|
||||
color: string;
|
||||
colors?: undefined;
|
||||
seriesType?: SeriesType.Normal | SeriesType.Area;
|
||||
title: string;
|
||||
options?: DeepPartialLineOptions;
|
||||
defaultVisible?: boolean;
|
||||
}
|
||||
)[];
|
||||
datasets: Datasets;
|
||||
activeResources: Accessor<Set<ResourceDataset<any, any>>>;
|
||||
}): PresetLegend {
|
||||
const { halved } = priceScaleOptions || {};
|
||||
|
||||
const price = applyPriceSeries({
|
||||
chart,
|
||||
datasets,
|
||||
preset,
|
||||
dataset: priceDataset,
|
||||
activeResources,
|
||||
options: {
|
||||
...priceOptions,
|
||||
halved,
|
||||
},
|
||||
});
|
||||
|
||||
const legendList: PresetLegend = [price.lineLegend, price.ohlcLegend];
|
||||
|
||||
const isAnyArea = list.find(
|
||||
(config) => config.seriesType === SeriesType.Area,
|
||||
);
|
||||
|
||||
const rightPriceScaleOptions = resetRightPriceScale(chart, {
|
||||
...priceScaleOptions,
|
||||
...(isAnyArea
|
||||
? {
|
||||
scaleMargins: {
|
||||
bottom: 0,
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
});
|
||||
|
||||
[...list]
|
||||
.reverse()
|
||||
.forEach(
|
||||
({
|
||||
dataset,
|
||||
color,
|
||||
colors,
|
||||
seriesType: type,
|
||||
title,
|
||||
options,
|
||||
defaultVisible,
|
||||
}) => {
|
||||
let series: ISeriesApi<"Baseline" | "Line" | "Area" | "Histogram">;
|
||||
|
||||
if (type === SeriesType.Based) {
|
||||
series = createBaseLineSeries(chart, {
|
||||
color,
|
||||
...options,
|
||||
});
|
||||
} else if (type === SeriesType.Area) {
|
||||
series = createAreaSeries(chart, {
|
||||
color,
|
||||
autoscaleInfoProvider: (getInfo: () => AutoscaleInfo | null) => {
|
||||
const info = getInfo();
|
||||
if (info) {
|
||||
info.priceRange.minValue = 0;
|
||||
}
|
||||
return info;
|
||||
},
|
||||
...options,
|
||||
});
|
||||
} else if (type === SeriesType.Histogram) {
|
||||
series = createHistogramSeries(chart, {
|
||||
color,
|
||||
...options,
|
||||
});
|
||||
} else {
|
||||
series = createLineSeries(chart, {
|
||||
color,
|
||||
...options,
|
||||
});
|
||||
}
|
||||
|
||||
legendList.splice(
|
||||
0,
|
||||
0,
|
||||
createSeriesLegend({
|
||||
id: stringToId(title),
|
||||
presetId: preset.id,
|
||||
title,
|
||||
series,
|
||||
color: () => colors || color || DEFAULT_BASELINE_COLORS,
|
||||
defaultVisible,
|
||||
url: dataset.url,
|
||||
}),
|
||||
);
|
||||
|
||||
createEffect(() => {
|
||||
series.setData(dataset?.values() || []);
|
||||
|
||||
setTimeScale(chartState.range);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
createEffect(() => {
|
||||
const options = {
|
||||
scaleMargins: {
|
||||
top:
|
||||
price.lineLegend.visible() || price.ohlcLegend.visible()
|
||||
? rightPriceScaleOptions.scaleMargins.top
|
||||
: rightPriceScaleOptions.scaleMargins.bottom,
|
||||
bottom: rightPriceScaleOptions.scaleMargins.bottom,
|
||||
},
|
||||
};
|
||||
|
||||
chart.priceScale("right").applyOptions(options);
|
||||
});
|
||||
|
||||
return legendList;
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { colors } from "../../utils/colors";
|
||||
import { applyMultipleSeries } from "../templates/multiple";
|
||||
import { applySeriesList } from "../apply";
|
||||
|
||||
export function createPresets(scale: ResourceScale) {
|
||||
return {
|
||||
@@ -12,12 +12,9 @@ export function createPresets(scale: ResourceScale) {
|
||||
title: "Transaction Count",
|
||||
description: "",
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: "1M SMA",
|
||||
color: colors.momentumYellow,
|
||||
@@ -48,12 +45,9 @@ export function createPresets(scale: ResourceScale) {
|
||||
title: "Transaction Volume",
|
||||
description: "",
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: "1M SMA",
|
||||
color: colors.momentumYellow,
|
||||
@@ -80,13 +74,12 @@ export function createPresets(scale: ResourceScale) {
|
||||
title: "Transaction Volume In Dollars",
|
||||
description: "",
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
mode: 1,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: "1M SMA",
|
||||
color: colors.lightDollars,
|
||||
@@ -124,12 +117,9 @@ export function createPresets(scale: ResourceScale) {
|
||||
title: "Annualized Transaction Volume",
|
||||
description: "",
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: "Volume",
|
||||
color: colors.bitcoin,
|
||||
@@ -147,12 +137,9 @@ export function createPresets(scale: ResourceScale) {
|
||||
title: "Annualized Transaction Volume In Dollars",
|
||||
description: "",
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: "Volume",
|
||||
color: colors.dollars,
|
||||
@@ -173,12 +160,9 @@ export function createPresets(scale: ResourceScale) {
|
||||
title: "Transactions Velocity",
|
||||
description: "",
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: "Transactions Velocity",
|
||||
color: colors.bitcoin,
|
||||
@@ -195,12 +179,9 @@ export function createPresets(scale: ResourceScale) {
|
||||
title: "Transactions Per Second",
|
||||
description: "",
|
||||
applyPreset(params) {
|
||||
return applyMultipleSeries({
|
||||
return applySeriesList({
|
||||
...params,
|
||||
priceScaleOptions: {
|
||||
halved: true,
|
||||
},
|
||||
list: [
|
||||
bottom: [
|
||||
{
|
||||
title: "1M SMA",
|
||||
color: colors.lightBitcoin,
|
||||
|
||||
Vendored
+16
-6
@@ -20,13 +20,14 @@ type FilePath = {
|
||||
}[];
|
||||
|
||||
type ApplyPreset = (params: {
|
||||
chart: IChartApi;
|
||||
charts: RWS<IChartApi[]>;
|
||||
parentDiv: HTMLDivElement;
|
||||
datasets: Datasets;
|
||||
preset: Preset;
|
||||
activeResources: Accessor<Set<ResourceDataset<any, any>>>;
|
||||
}) => ApplyPresetReturn;
|
||||
|
||||
type ApplyPresetReturn = PresetLegend;
|
||||
legendSetter: Setter<SeriesLegend[]>;
|
||||
dark: Accessor<boolean>;
|
||||
activeIds: RWS<number[]>;
|
||||
}) => void;
|
||||
|
||||
interface PartialPresetFolder {
|
||||
name: string;
|
||||
@@ -59,4 +60,13 @@ interface Presets {
|
||||
select(preset: Preset): void;
|
||||
}
|
||||
|
||||
type PresetLegend = SeriesLegend[];
|
||||
type PriceSeriesType = "Candlestick" | "Line";
|
||||
|
||||
interface ChartObject {
|
||||
scale: ResourceScale;
|
||||
div: HTMLDivElement;
|
||||
chart: IChartApi;
|
||||
whitespace: ISeriesApi<"Line">;
|
||||
legendList: SeriesLegend[];
|
||||
debouncedSetMinMaxMarkers: VoidFunction;
|
||||
}
|
||||
|
||||
+122
-40
@@ -41,48 +41,129 @@ import {
|
||||
// DO NOT USE TRANSPARENCY HERE
|
||||
// ---
|
||||
|
||||
const lightRed = redTailwind[300];
|
||||
const red = redTailwind[500];
|
||||
const darkRed = redTailwind[900];
|
||||
const orange = orangeTailwind[500];
|
||||
const darkOrange = orangeTailwind[900];
|
||||
const amber = amberTailwind[500];
|
||||
const darkAmber = amberTailwind[900];
|
||||
const yellow = yellowTailwind[500];
|
||||
const darkYellow = yellowTailwind[500];
|
||||
const lime = limeTailwind[500];
|
||||
const darkLime = limeTailwind[900];
|
||||
const green = greenTailwind[500];
|
||||
const darkGreen = greenTailwind[900];
|
||||
const lightEmerald = emeraldTailwind[300];
|
||||
const emerald = emeraldTailwind[500];
|
||||
const darkEmerald = emeraldTailwind[900];
|
||||
const teal = tealTailwind[500];
|
||||
const darkTeal = tealTailwind[900];
|
||||
const cyan = cyanTailwind[500];
|
||||
const darkCyan = cyanTailwind[900];
|
||||
const sky = skyTailwind[500];
|
||||
const darkSky = skyTailwind[900];
|
||||
const blue = blueTailwind[500];
|
||||
const darkBlue = blueTailwind[900];
|
||||
const indigo = indigoTailwind[500];
|
||||
const darkIndigo = indigoTailwind[900];
|
||||
const violet = violetTailwind[500];
|
||||
const darkViolet = violetTailwind[900];
|
||||
const purple = purpleTailwind[500];
|
||||
const darkPurple = purpleTailwind[900];
|
||||
const fuchsia = fuchsiaTailwind[500];
|
||||
const darkFuchsia = fuchsiaTailwind[900];
|
||||
const pink = pinkTailwind[500];
|
||||
const darkPink = pinkTailwind[900];
|
||||
const rose = roseTailwind[500];
|
||||
const darkRose = roseTailwind[900];
|
||||
function lightRed(dark: Accessor<boolean>) {
|
||||
return dark() ? redTailwind[300] : redTailwind[800];
|
||||
}
|
||||
function red(dark: Accessor<boolean>) {
|
||||
return dark() ? redTailwind[500] : redTailwind[600];
|
||||
}
|
||||
function darkRed(dark: Accessor<boolean>) {
|
||||
return dark() ? redTailwind[900] : redTailwind[100];
|
||||
}
|
||||
function orange(dark: Accessor<boolean>) {
|
||||
return dark() ? orangeTailwind[500] : orangeTailwind[600];
|
||||
}
|
||||
function darkOrange(dark: Accessor<boolean>) {
|
||||
return dark() ? orangeTailwind[900] : orangeTailwind[100];
|
||||
}
|
||||
function amber(dark: Accessor<boolean>) {
|
||||
return dark() ? amberTailwind[500] : amberTailwind[600];
|
||||
}
|
||||
function darkAmber(dark: Accessor<boolean>) {
|
||||
return dark() ? amberTailwind[900] : amberTailwind[100];
|
||||
}
|
||||
function yellow(dark: Accessor<boolean>) {
|
||||
return dark() ? yellowTailwind[500] : yellowTailwind[600];
|
||||
}
|
||||
function darkYellow(dark: Accessor<boolean>) {
|
||||
return dark() ? yellowTailwind[500] : yellowTailwind[600];
|
||||
}
|
||||
function lime(dark: Accessor<boolean>) {
|
||||
return dark() ? limeTailwind[500] : limeTailwind[600];
|
||||
}
|
||||
function darkLime(dark: Accessor<boolean>) {
|
||||
return dark() ? limeTailwind[900] : limeTailwind[100];
|
||||
}
|
||||
function green(dark: Accessor<boolean>) {
|
||||
return dark() ? greenTailwind[500] : greenTailwind[600];
|
||||
}
|
||||
function darkGreen(dark: Accessor<boolean>) {
|
||||
return dark() ? greenTailwind[900] : greenTailwind[100];
|
||||
}
|
||||
function lightEmerald(dark: Accessor<boolean>) {
|
||||
return dark() ? emeraldTailwind[300] : emeraldTailwind[800];
|
||||
}
|
||||
function emerald(dark: Accessor<boolean>) {
|
||||
return dark() ? emeraldTailwind[500] : emeraldTailwind[600];
|
||||
}
|
||||
function darkEmerald(dark: Accessor<boolean>) {
|
||||
return dark() ? emeraldTailwind[900] : emeraldTailwind[100];
|
||||
}
|
||||
function teal(dark: Accessor<boolean>) {
|
||||
return dark() ? tealTailwind[500] : tealTailwind[600];
|
||||
}
|
||||
function darkTeal(dark: Accessor<boolean>) {
|
||||
return dark() ? tealTailwind[900] : tealTailwind[100];
|
||||
}
|
||||
function cyan(dark: Accessor<boolean>) {
|
||||
return dark() ? cyanTailwind[500] : cyanTailwind[600];
|
||||
}
|
||||
function darkCyan(dark: Accessor<boolean>) {
|
||||
return dark() ? cyanTailwind[900] : cyanTailwind[100];
|
||||
}
|
||||
function sky(dark: Accessor<boolean>) {
|
||||
return dark() ? skyTailwind[500] : skyTailwind[600];
|
||||
}
|
||||
function darkSky(dark: Accessor<boolean>) {
|
||||
return dark() ? skyTailwind[900] : skyTailwind[100];
|
||||
}
|
||||
function blue(dark: Accessor<boolean>) {
|
||||
return dark() ? blueTailwind[500] : blueTailwind[600];
|
||||
}
|
||||
function darkBlue(dark: Accessor<boolean>) {
|
||||
return dark() ? blueTailwind[900] : blueTailwind[100];
|
||||
}
|
||||
function indigo(dark: Accessor<boolean>) {
|
||||
return dark() ? indigoTailwind[500] : indigoTailwind[600];
|
||||
}
|
||||
function darkIndigo(dark: Accessor<boolean>) {
|
||||
return dark() ? indigoTailwind[900] : indigoTailwind[100];
|
||||
}
|
||||
function violet(dark: Accessor<boolean>) {
|
||||
return dark() ? violetTailwind[500] : violetTailwind[600];
|
||||
}
|
||||
function darkViolet(dark: Accessor<boolean>) {
|
||||
return dark() ? violetTailwind[900] : violetTailwind[100];
|
||||
}
|
||||
function purple(dark: Accessor<boolean>) {
|
||||
return dark() ? purpleTailwind[500] : purpleTailwind[600];
|
||||
}
|
||||
function darkPurple(dark: Accessor<boolean>) {
|
||||
return dark() ? purpleTailwind[900] : purpleTailwind[100];
|
||||
}
|
||||
function fuchsia(dark: Accessor<boolean>) {
|
||||
return dark() ? fuchsiaTailwind[500] : fuchsiaTailwind[600];
|
||||
}
|
||||
function darkFuchsia(dark: Accessor<boolean>) {
|
||||
return dark() ? fuchsiaTailwind[900] : fuchsiaTailwind[100];
|
||||
}
|
||||
function pink(dark: Accessor<boolean>) {
|
||||
return dark() ? pinkTailwind[500] : pinkTailwind[600];
|
||||
}
|
||||
function darkPink(dark: Accessor<boolean>) {
|
||||
return dark() ? pinkTailwind[900] : pinkTailwind[100];
|
||||
}
|
||||
function rose(dark: Accessor<boolean>) {
|
||||
return dark() ? roseTailwind[500] : roseTailwind[600];
|
||||
}
|
||||
function darkRose(dark: Accessor<boolean>) {
|
||||
return dark() ? roseTailwind[900] : roseTailwind[100];
|
||||
}
|
||||
|
||||
const darkWhite = grayTailwind[400];
|
||||
const gray = grayTailwind[600];
|
||||
function darkWhite(dark: Accessor<boolean>) {
|
||||
return dark() ? grayTailwind[400] : grayTailwind[400];
|
||||
}
|
||||
function gray(dark: Accessor<boolean>) {
|
||||
return dark() ? grayTailwind[600] : grayTailwind[400];
|
||||
}
|
||||
|
||||
const black = "#000000";
|
||||
const white = "#ffffff";
|
||||
function white(dark: Accessor<boolean>) {
|
||||
return dark() ? "#ffffff" : "#000000";
|
||||
}
|
||||
|
||||
function black(dark: Accessor<boolean>) {
|
||||
return dark() ? "#000000" : "#ffffff";
|
||||
}
|
||||
|
||||
export const convertCandleToCandleColor = (
|
||||
candle: { close: number; open: number },
|
||||
@@ -110,6 +191,7 @@ export const convertCandleToVolumeColor = (
|
||||
|
||||
export const colors = {
|
||||
white,
|
||||
black,
|
||||
darkWhite,
|
||||
gray,
|
||||
lightBitcoin: yellow,
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
// import { ONE_DAY_IN_MS } from "./time";
|
||||
|
||||
import { ONE_DAY_IN_MS } from "./time";
|
||||
|
||||
export const dateToString = (date: Date) => date.toJSON().split("T")[0];
|
||||
|
||||
// export const FIVE_MONTHS_IN_DAYS = 30 * 5;
|
||||
|
||||
export const getNumberOfDaysBetweenTwoDates = (oldest: Date, youngest: Date) =>
|
||||
Math.round(Math.abs((youngest.valueOf() - oldest.valueOf()) / ONE_DAY_IN_MS));
|
||||
Math.round(Math.abs((youngest.getTime() - oldest.getTime()) / ONE_DAY_IN_MS));
|
||||
|
||||
@@ -1,31 +1,52 @@
|
||||
export const priceToUSLocale = (price: number, compact = true) => {
|
||||
const absolutePrice = Math.abs(price);
|
||||
const lessThan100 = absolutePrice < 100;
|
||||
const lessThan1000 = absolutePrice < 1_000;
|
||||
const biggerThanMillion = absolutePrice >= 1_000_000;
|
||||
const suffices = ["M", "B", "T", "Q"];
|
||||
|
||||
return numberToUSLocale(
|
||||
price,
|
||||
lessThan1000 ? (lessThan100 ? 2 : 1) : biggerThanMillion ? 3 : 0,
|
||||
biggerThanMillion && compact
|
||||
? {
|
||||
notation: "compact",
|
||||
compactDisplay: "short",
|
||||
}
|
||||
: undefined,
|
||||
);
|
||||
};
|
||||
export function valueToString(value: number) {
|
||||
const absoluteValue = Math.abs(value);
|
||||
|
||||
export const percentageToUSLocale = (percentage: number) =>
|
||||
numberToUSLocale(percentage, 1);
|
||||
// value = absoluteValue;
|
||||
|
||||
const numberToUSLocale = (
|
||||
if (isNaN(value)) {
|
||||
return "";
|
||||
// } else if (value === 0) {
|
||||
// return "0";
|
||||
} else if (absoluteValue < 10) {
|
||||
return numberToUSLocale(value, 3);
|
||||
} else if (absoluteValue < 100) {
|
||||
return numberToUSLocale(value, 2);
|
||||
} else if (absoluteValue < 1_000) {
|
||||
return numberToUSLocale(value, 1);
|
||||
} else if (absoluteValue < 100_000) {
|
||||
return numberToUSLocale(value, 0);
|
||||
} else if (absoluteValue < 1_000_000) {
|
||||
return `${numberToUSLocale(value / 1_000, 1)}K`;
|
||||
} else if (absoluteValue >= 1_000_000_000_000_000_000) {
|
||||
return "Inf.";
|
||||
}
|
||||
|
||||
const log = Math.floor(Math.log10(absoluteValue) - 6);
|
||||
|
||||
const letterIndex = Math.floor(log / 3);
|
||||
const letter = suffices[letterIndex];
|
||||
|
||||
const modulused = log % 3;
|
||||
|
||||
if (modulused === 0) {
|
||||
return `${numberToUSLocale(value / (1_000_000 * 1_000 ** letterIndex), 3)}${letter}`;
|
||||
} else if (modulused === 1) {
|
||||
return `${numberToUSLocale(value / (1_000_000 * 1_000 ** letterIndex), 2)}${letter}`;
|
||||
} else {
|
||||
return `${numberToUSLocale(value / (1_000_000 * 1_000 ** letterIndex), 1)}${letter}`;
|
||||
}
|
||||
}
|
||||
|
||||
function numberToUSLocale(
|
||||
value: number,
|
||||
digits: number,
|
||||
digits?: number,
|
||||
options?: Intl.NumberFormatOptions | undefined,
|
||||
) =>
|
||||
value.toLocaleString("en-us", {
|
||||
) {
|
||||
return value.toLocaleString("en-us", {
|
||||
...options,
|
||||
minimumFractionDigits: digits,
|
||||
maximumFractionDigits: digits,
|
||||
});
|
||||
}
|
||||
|
||||
+12
-12
@@ -1,7 +1,7 @@
|
||||
import { createRWS } from "/src/solid/rws";
|
||||
|
||||
export const createSelectableList = <T, L extends T[] = T[]>(
|
||||
list: L,
|
||||
export const createDynamicList = <T, L extends T[] = T[]>(
|
||||
l: L,
|
||||
parameters?: {
|
||||
selected?: L[number];
|
||||
selectedIndex?: number | null;
|
||||
@@ -10,10 +10,10 @@ export const createSelectableList = <T, L extends T[] = T[]>(
|
||||
const selected = createRWS<L[number] | null>(null);
|
||||
const selectedIndex = createRWS<number | null>(null);
|
||||
|
||||
const selectableList: SelectableList<L[number], L> = {
|
||||
const list: DynamicList<L[number], L> = {
|
||||
selected,
|
||||
selectedIndex,
|
||||
list: createRWS(list, {
|
||||
list: createRWS(l, {
|
||||
equals: false,
|
||||
}),
|
||||
select(s) {
|
||||
@@ -83,10 +83,10 @@ export const createSelectableList = <T, L extends T[] = T[]>(
|
||||
toJSON<TJSON, LJSON extends TJSON[] = TJSON[]>(
|
||||
transform: (value: T) => TJSON,
|
||||
filter?: (value: T) => boolean,
|
||||
): JSONSelectableList<TJSON, LJSON> {
|
||||
): JSONDynamicList<TJSON, LJSON> {
|
||||
return {
|
||||
version: 1,
|
||||
selectedIndex: getIndexOfSelectedInSelectableList(this),
|
||||
selectedIndex: getIndexOfSelectedInDynamicList(this),
|
||||
list: (filter ? this.list().filter(filter) : this.list()).map((value) =>
|
||||
transform(value),
|
||||
) as LJSON,
|
||||
@@ -95,18 +95,18 @@ export const createSelectableList = <T, L extends T[] = T[]>(
|
||||
};
|
||||
|
||||
if (parameters?.selected !== undefined) {
|
||||
selectableList.select(parameters.selected);
|
||||
list.select(parameters.selected);
|
||||
} else if (parameters?.selectedIndex !== undefined) {
|
||||
selectableList.selectIndex(parameters.selectedIndex);
|
||||
list.selectIndex(parameters.selectedIndex);
|
||||
}
|
||||
|
||||
return selectableList;
|
||||
return list;
|
||||
};
|
||||
|
||||
export const createSL = createSelectableList;
|
||||
export const createDSL = createDynamicList;
|
||||
|
||||
export const getIndexOfSelectedInSelectableList = <T, L extends T[] = T[]>(
|
||||
sl: SelectableList<L[number], L>,
|
||||
export const getIndexOfSelectedInDynamicList = <T, L extends T[] = T[]>(
|
||||
sl: DynamicList<L[number], L>,
|
||||
) => {
|
||||
const selected = sl.selected();
|
||||
|
||||
+3
-3
@@ -2,7 +2,7 @@
|
||||
// JSON
|
||||
// ---
|
||||
|
||||
interface JSONSelectableList<T, L extends T[] = T[]> {
|
||||
interface JSONDynamicList<T, L extends T[] = T[]> {
|
||||
readonly version: 1;
|
||||
selectedIndex: number | null;
|
||||
readonly list: L;
|
||||
@@ -12,7 +12,7 @@ interface JSONSelectableList<T, L extends T[] = T[]> {
|
||||
// Object
|
||||
// ---
|
||||
|
||||
interface SelectableList<T, L extends T[] = T[]> {
|
||||
interface DynamicList<T, L extends T[] = T[]> {
|
||||
readonly selected: Accessor<T | null>;
|
||||
readonly selectedIndex: Accessor<number | null>;
|
||||
readonly list: RWS<L>;
|
||||
@@ -29,5 +29,5 @@ interface SelectableList<T, L extends T[] = T[]> {
|
||||
readonly toJSON: <TJSON, LJSON extends TJSON[] = TJSON[]>(
|
||||
transform: (value: T) => LJSON[number],
|
||||
filter?: (value: T) => boolean,
|
||||
) => JSONSelectableList<TJSON, LJSON>;
|
||||
) => JSONDynamicList<TJSON, LJSON>;
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
import { createRWS } from "/src/solid/rws";
|
||||
|
||||
import { run } from "../../run";
|
||||
|
||||
export const createStaticList = <T, L extends T[] = T[]>(
|
||||
l: L,
|
||||
parameters: {
|
||||
selected?: L[number];
|
||||
selectedIndex?: number;
|
||||
saveable?: {
|
||||
mode: "localStorage" | "URLParams" | "both";
|
||||
key: string;
|
||||
};
|
||||
defaultValue?: L[number];
|
||||
defaultIndex?: number;
|
||||
},
|
||||
) => {
|
||||
if (
|
||||
!l.length ||
|
||||
(parameters.saveable === undefined &&
|
||||
parameters.selected === undefined &&
|
||||
parameters.selectedIndex === undefined)
|
||||
) {
|
||||
throw Error("not possible");
|
||||
}
|
||||
|
||||
const selected = createRWS<L[number]>(
|
||||
run(() => {
|
||||
let savedIndex: number | undefined;
|
||||
|
||||
if (parameters.saveable) {
|
||||
if (parameters.saveable.mode !== "localStorage") {
|
||||
throw Error("unsupported");
|
||||
}
|
||||
|
||||
const savedRaw = localStorage.getItem(parameters.saveable.key);
|
||||
|
||||
if (savedRaw) {
|
||||
savedIndex = Number(savedRaw);
|
||||
}
|
||||
}
|
||||
|
||||
if (parameters.selected) {
|
||||
const found = l.find((v) => v === parameters.selected);
|
||||
|
||||
if (!found) {
|
||||
throw Error("unreachable");
|
||||
}
|
||||
|
||||
return found;
|
||||
} else {
|
||||
return (
|
||||
l.at(savedIndex ?? parameters.selectedIndex!) ??
|
||||
parameters.defaultValue ??
|
||||
l[parameters.defaultIndex || 0]
|
||||
);
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
const selectedIndex = createRWS<number>(
|
||||
run(() => {
|
||||
if (
|
||||
parameters.selectedIndex !== null &&
|
||||
parameters.selectedIndex !== undefined
|
||||
) {
|
||||
const found = l.at(parameters.selectedIndex);
|
||||
|
||||
if (!found) {
|
||||
throw Error("unreachable");
|
||||
}
|
||||
|
||||
return parameters.selectedIndex;
|
||||
} else {
|
||||
return l.indexOf(selected());
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
createEffect(() => {
|
||||
if (parameters.saveable) {
|
||||
localStorage.setItem(parameters.saveable.key, String(selectedIndex()));
|
||||
}
|
||||
});
|
||||
|
||||
const list: StaticList<L[number], L> = {
|
||||
selected,
|
||||
selectedIndex,
|
||||
list: createRWS(l, {
|
||||
equals: false,
|
||||
}),
|
||||
select(s) {
|
||||
if (this.selected() !== s) {
|
||||
batch(() => {
|
||||
selected.set(() => s);
|
||||
this.selectIndex(this.list().indexOf(s));
|
||||
});
|
||||
}
|
||||
},
|
||||
selectIndex(i) {
|
||||
if (i && (i < 0 || i >= this.list().length)) {
|
||||
throw new Error(
|
||||
`SelectableList: selectIndex: ${i} is incorrect ! (has ${
|
||||
this.list().length
|
||||
} elements)`,
|
||||
);
|
||||
}
|
||||
|
||||
if (i !== this.selectedIndex()) {
|
||||
selectedIndex.set(i);
|
||||
|
||||
const value = this.list().at(i);
|
||||
|
||||
if (value === undefined) {
|
||||
throw Error("unreachable");
|
||||
}
|
||||
|
||||
this.select(value);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
if (parameters?.selected !== undefined) {
|
||||
list.select(parameters.selected);
|
||||
} else if (parameters?.selectedIndex !== undefined) {
|
||||
list.selectIndex(parameters.selectedIndex);
|
||||
}
|
||||
|
||||
return list;
|
||||
};
|
||||
|
||||
export const createSL = createStaticList;
|
||||
|
||||
export const getIndexOfSelectedInStaticList = <T, L extends T[] = T[]>(
|
||||
sl: StaticList<L[number], L>,
|
||||
) => {
|
||||
const selected = sl.selected();
|
||||
|
||||
return selected ? sl.list().indexOf(selected) : null;
|
||||
};
|
||||
@@ -0,0 +1,9 @@
|
||||
interface StaticList<T, L extends T[] = T[]> {
|
||||
readonly selected: Accessor<T>;
|
||||
readonly selectedIndex: Accessor<number>;
|
||||
readonly list: RWS<L>;
|
||||
readonly select: <S extends L[number] = L[number]>(s: S) => void;
|
||||
readonly selectIndex: (index: number) => void;
|
||||
}
|
||||
|
||||
type SL<T, L extends T[] = T[]> = StaticList<T, L>;
|
||||
@@ -3,7 +3,3 @@ export function sleep(ms: number) {
|
||||
setTimeout(resolve, ms);
|
||||
});
|
||||
}
|
||||
|
||||
export function tick() {
|
||||
return sleep(1);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
import { sleep } from "./sleep";
|
||||
|
||||
export function tick() {
|
||||
return sleep(0);
|
||||
}
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
type Color = (dark: Accessor<boolean>) => string;
|
||||
@@ -27,18 +27,18 @@ export const krakenAPI = {
|
||||
|
||||
const [timestamp, _, open, high, low, close, __, volume] = result[1];
|
||||
|
||||
const dateStr = dateToString(new Date(Number(timestamp) * 1000));
|
||||
const date = new Date(Number(timestamp) * 1000);
|
||||
|
||||
const dateStr = dateToString(date);
|
||||
|
||||
const candle: DatasetCandlestickData = {
|
||||
// date: dateStr,
|
||||
number: new Date(dateStr).valueOf() / ONE_DAY_IN_MS,
|
||||
time: dateStr,
|
||||
year: date.getUTCFullYear(),
|
||||
open: Number(open),
|
||||
high: Number(high),
|
||||
low: Number(low),
|
||||
close: Number(close),
|
||||
value: Number(close),
|
||||
// volume: Number(volume),
|
||||
};
|
||||
|
||||
candle && callback({ ...candle });
|
||||
|
||||
+42
-2
@@ -21,10 +21,50 @@
|
||||
}
|
||||
}
|
||||
|
||||
html {
|
||||
/* Foreground, Background */
|
||||
scrollbar-color: #ffffff66 #00000066;
|
||||
scrollbar-width: thin;
|
||||
}
|
||||
|
||||
a {
|
||||
@apply text-orange-300 hover:underline;
|
||||
@apply text-orange-700 hover:underline dark:text-orange-300;
|
||||
}
|
||||
|
||||
mark {
|
||||
@apply bg-transparent p-0 text-orange-400;
|
||||
@apply bg-transparent p-0 text-orange-600 dark:text-orange-400;
|
||||
}
|
||||
|
||||
strong {
|
||||
@apply text-orange-600 dark:text-orange-400;
|
||||
}
|
||||
|
||||
/* Hide scrollbar for Chrome, Safari and Opera */
|
||||
.no-scrollbar::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Hide scrollbar for IE, Edge and Firefox */
|
||||
.no-scrollbar {
|
||||
-ms-overflow-style: none; /* IE and Edge */
|
||||
scrollbar-width: none; /* Firefox */
|
||||
}
|
||||
|
||||
.border-light {
|
||||
@apply border-orange-800/25 dark:border-orange-200/25;
|
||||
}
|
||||
|
||||
.border-lighter {
|
||||
@apply border-orange-800/[0.125] dark:border-orange-200/[0.125];
|
||||
}
|
||||
|
||||
.border-superlight {
|
||||
@apply border-orange-800/5 dark:border-orange-200/[0.0625];
|
||||
}
|
||||
|
||||
.text-high-contrast {
|
||||
@apply text-orange-950 dark:text-orange-50;
|
||||
}
|
||||
.decoration-high-contrast {
|
||||
@apply decoration-orange-950 dark:decoration-orange-50;
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user