mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-06-11 07:23:32 -07:00
general: snapshot
This commit is contained in:
+4
-1
@@ -7,7 +7,7 @@
|
||||
### App
|
||||
|
||||
- General
|
||||
- Added height datasets and many optimizations to make them usable
|
||||
- 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
|
||||
@@ -24,6 +24,9 @@
|
||||
- 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
|
||||
|
||||
@@ -40,11 +40,12 @@ Adjectives that describe what this project is or strives to be, in no particular
|
||||
- **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
|
||||
- **Transparent**: You can verify and see exactly how each dataset is computed
|
||||
- **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**: Many many different datasets which can be viewed in chart, dashbards or raw values
|
||||
- **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
|
||||
|
||||
## Milestones
|
||||
@@ -57,6 +58,7 @@ Big features that are planned, in no particular order:
|
||||
- **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_:
|
||||
|
||||
|
||||
@@ -38,6 +38,7 @@ const texts = [
|
||||
"nyknyc",
|
||||
"low time preference",
|
||||
"absolute scarcity",
|
||||
"time is scarce",
|
||||
];
|
||||
|
||||
export function Background({
|
||||
|
||||
@@ -6,8 +6,8 @@ export function Chart({
|
||||
presets,
|
||||
datasets,
|
||||
legendSetter,
|
||||
dark: _dark,
|
||||
activeRange,
|
||||
dark,
|
||||
activeIds,
|
||||
}: {
|
||||
charts: RWS<IChartApi[]>;
|
||||
parentDiv: RWS<HTMLDivElement | undefined>;
|
||||
@@ -15,7 +15,7 @@ export function Chart({
|
||||
datasets: Datasets;
|
||||
legendSetter: Setter<SeriesLegend[]>;
|
||||
dark: Accessor<boolean>;
|
||||
activeRange: RWS<number[]>;
|
||||
activeIds: RWS<number[]>;
|
||||
}) {
|
||||
const wasIdle = createRWS(false);
|
||||
|
||||
@@ -44,7 +44,6 @@ export function Chart({
|
||||
createEffect(() => {
|
||||
const preset = presets.selected();
|
||||
const div = parentDiv();
|
||||
const dark = _dark();
|
||||
|
||||
if (!wasIdle() || !div) return;
|
||||
|
||||
@@ -58,7 +57,7 @@ export function Chart({
|
||||
preset,
|
||||
legendSetter,
|
||||
dark,
|
||||
activeRange,
|
||||
activeIds,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("chart: render: failed", error);
|
||||
|
||||
@@ -8,11 +8,13 @@ const transparency = "44";
|
||||
export function Legend({
|
||||
scale,
|
||||
legend: legendList,
|
||||
activeRange,
|
||||
dark,
|
||||
activeIds,
|
||||
}: {
|
||||
scale: Accessor<ResourceScale>;
|
||||
legend: Accessor<SeriesLegend[]>;
|
||||
activeRange: Accessor<number[]>;
|
||||
dark: Accessor<boolean>;
|
||||
activeIds: Accessor<number[]>;
|
||||
}) {
|
||||
const hovered = createRWS<SeriesLegend | undefined>(undefined);
|
||||
|
||||
@@ -23,7 +25,7 @@ export function Legend({
|
||||
<For each={legendList()}>
|
||||
{(legend) => {
|
||||
createEffect(() => {
|
||||
const range = activeRange();
|
||||
const range = activeIds();
|
||||
|
||||
for (let i = 0; i < range.length; i++) {
|
||||
const id = range[i];
|
||||
@@ -121,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) => (
|
||||
|
||||
@@ -32,7 +32,7 @@ export function ChartFrame({
|
||||
|
||||
const scale = createMemo(() => presets.selected().scale);
|
||||
|
||||
const activeRange = createRWS([] as number[], { equals: false });
|
||||
const activeIds = createRWS([] as number[], { equals: false });
|
||||
|
||||
const Chart = lazy(() =>
|
||||
import("./components/chart").then((d) => ({ default: d.Chart })),
|
||||
@@ -55,7 +55,12 @@ export function ChartFrame({
|
||||
<div class="border-lighter border-t" />
|
||||
|
||||
<div class="flex">
|
||||
<Legend legend={legend} scale={scale} activeRange={activeRange} />
|
||||
<Legend
|
||||
legend={legend}
|
||||
scale={scale}
|
||||
activeIds={activeIds}
|
||||
dark={dark}
|
||||
/>
|
||||
|
||||
<div class="border-lighter border-l" />
|
||||
|
||||
@@ -71,7 +76,7 @@ export function ChartFrame({
|
||||
legendSetter={legend.set}
|
||||
presets={presets}
|
||||
dark={dark}
|
||||
activeRange={activeRange}
|
||||
activeIds={activeIds}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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());
|
||||
@@ -174,6 +180,7 @@ 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-current text-xs font-bold">
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import { version } from "/src/../package.json";
|
||||
import { chrome, ipad, iphone, macOS, safari, 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({
|
||||
@@ -27,7 +32,7 @@ export function SettingsFrame({
|
||||
<div class="border-lighter -mx-4 border-t" />
|
||||
|
||||
<div class="space-y-4">
|
||||
<p class="text-base font-medium">General</p>
|
||||
<Title>General</Title>
|
||||
|
||||
<RadioGroup
|
||||
title="Theme"
|
||||
@@ -40,7 +45,7 @@ export function SettingsFrame({
|
||||
<div class="border-lighter -mx-4 border-t" />
|
||||
|
||||
<div class="space-y-4">
|
||||
<p class="text-base font-medium">Background</p>
|
||||
<Title>Background</Title>
|
||||
|
||||
<RadioGroup
|
||||
title="Mode"
|
||||
@@ -58,20 +63,148 @@ export function SettingsFrame({
|
||||
</div>
|
||||
|
||||
<hr class="border-lighter -mx-4 border-t" />
|
||||
<p class="text-center">
|
||||
<span class="opacity-50">Version:</span>{" "}
|
||||
<a
|
||||
href="https://codeberg.org/satonomics/satonomics/src/branch/main/CHANGELOG.md"
|
||||
target="_blank"
|
||||
>
|
||||
{version}
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<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 && !chrome && safari && (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>
|
||||
</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
|
||||
@@ -104,7 +237,7 @@ function RadioGroup<
|
||||
value === sl.selected()
|
||||
? "border-lighter bg-orange-50/75 shadow dark:bg-orange-200/10"
|
||||
: "border-transparent",
|
||||
"flex 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 sm:flex-1",
|
||||
"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
|
||||
|
||||
@@ -23,7 +23,7 @@ export function Clickable({
|
||||
? "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",
|
||||
"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",
|
||||
"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}
|
||||
|
||||
+25
-2
@@ -41,11 +41,34 @@ export function App() {
|
||||
|
||||
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" &&
|
||||
window.matchMedia("(prefers-color-scheme: dark)").matches)
|
||||
(appTheme.selected() === "System" && preferredSystemTheme() === "dark")
|
||||
) {
|
||||
dark.set(true);
|
||||
document.documentElement.classList.add("dark");
|
||||
|
||||
@@ -5,3 +5,19 @@ export const touchScreen =
|
||||
"ontouchstart" in window ||
|
||||
navigator.maxTouchPoints > 0 ||
|
||||
(navigator as any).msMaxTouchPoints > 0;
|
||||
|
||||
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 safari = navigator.userAgent.toLowerCase().includes("safari");
|
||||
|
||||
export const chrome = navigator.userAgent.toLowerCase().includes("chrome");
|
||||
|
||||
export const phone =
|
||||
/Android|webOS|iPhone|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
|
||||
navigator.userAgent,
|
||||
);
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
@@ -10,43 +10,51 @@ export const DEFAULT_BASELINE_COLORS = [
|
||||
DEFAULT_BASELINE_BOTTOM_COLOR,
|
||||
];
|
||||
|
||||
export const createBaseLineSeries = (
|
||||
chart: IChartApi,
|
||||
options: BaselineSeriesOptions,
|
||||
) => {
|
||||
const {
|
||||
title,
|
||||
color,
|
||||
topColor,
|
||||
topLineColor,
|
||||
bottomColor,
|
||||
bottomLineColor,
|
||||
base,
|
||||
lineColor,
|
||||
} = options;
|
||||
const transparent = `transparent`;
|
||||
|
||||
const allTopColor = topColor || color || DEFAULT_BASELINE_TOP_COLOR;
|
||||
const topFillColor = `transparent`;
|
||||
const allBottomColor = bottomColor || color || DEFAULT_BASELINE_BOTTOM_COLOR;
|
||||
const bottomFillColor = `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.options,
|
||||
...(base ? { baseValue: { type: "price", price: base } } : {}),
|
||||
topLineColor: topLineColor || lineColor || allTopColor,
|
||||
topFillColor1: topFillColor,
|
||||
topFillColor2: topFillColor,
|
||||
bottomLineColor: bottomLineColor || lineColor || allBottomColor,
|
||||
bottomFillColor1: bottomFillColor,
|
||||
bottomFillColor2: bottomFillColor,
|
||||
title,
|
||||
...(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;
|
||||
};
|
||||
|
||||
@@ -1,21 +1,18 @@
|
||||
import { colors } from "/src/scripts/utils/colors";
|
||||
|
||||
export const createCandlesticksSeries = (
|
||||
chart: IChartApi,
|
||||
options: PriceSeriesOptions = {},
|
||||
): [ISeriesApi<"Candlestick">, string[]] => {
|
||||
export const createCandlesticksSeries = ({
|
||||
chart,
|
||||
dark,
|
||||
options = {},
|
||||
}: {
|
||||
chart: IChartApi;
|
||||
dark: Accessor<boolean>;
|
||||
options?: PriceSeriesOptions;
|
||||
}): [ISeriesApi<"Candlestick">, Color[]] => {
|
||||
const { inverseColors } = options;
|
||||
|
||||
const upColor = inverseColors ? colors.loss : colors.profit;
|
||||
|
||||
const downColor = inverseColors ? colors.profit : colors.loss;
|
||||
|
||||
const candlestickSeries = chart.addCandlestickSeries({
|
||||
baseLineVisible: false,
|
||||
upColor,
|
||||
wickUpColor: upColor,
|
||||
downColor,
|
||||
wickDownColor: downColor,
|
||||
borderVisible: false,
|
||||
priceLineVisible: false,
|
||||
baseLineColor: "",
|
||||
@@ -25,5 +22,22 @@ export const createCandlesticksSeries = (
|
||||
...options.seriesOptions,
|
||||
});
|
||||
|
||||
return [candlestickSeries, [upColor, downColor]];
|
||||
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]];
|
||||
};
|
||||
|
||||
@@ -15,34 +15,24 @@ export function createChart(
|
||||
dark,
|
||||
priceScaleOptions,
|
||||
}: {
|
||||
dark: boolean;
|
||||
dark: Accessor<boolean>;
|
||||
priceScaleOptions: DeepPartialPriceScaleOptions;
|
||||
},
|
||||
) {
|
||||
console.log(`chart: create (scale: ${scale})`);
|
||||
|
||||
const { white, black } = colors;
|
||||
|
||||
const textColor = dark ? white : black;
|
||||
const borderColor = dark ? "#332F24" : "#F1E4E0";
|
||||
|
||||
const options: DeepPartialChartOptions = {
|
||||
autoSize: true,
|
||||
layout: {
|
||||
fontFamily: "Lexend",
|
||||
background: { color: "transparent" },
|
||||
fontSize: 14,
|
||||
textColor,
|
||||
},
|
||||
grid: {
|
||||
vertLines: { visible: false },
|
||||
horzLines: { visible: false },
|
||||
},
|
||||
rightPriceScale: {
|
||||
borderColor,
|
||||
},
|
||||
timeScale: {
|
||||
borderColor,
|
||||
minBarSpacing: 0.05,
|
||||
shiftVisibleRangeOnNewBar: false,
|
||||
allowShiftVisibleRangeOnWhitespaceReplacement: false,
|
||||
@@ -54,14 +44,6 @@ export function createChart(
|
||||
},
|
||||
crosshair: {
|
||||
mode: CrosshairMode.Normal,
|
||||
horzLine: {
|
||||
color: textColor,
|
||||
labelBackgroundColor: textColor,
|
||||
},
|
||||
vertLine: {
|
||||
color: textColor,
|
||||
labelBackgroundColor: textColor,
|
||||
},
|
||||
},
|
||||
localization: {
|
||||
priceFormatter: valueToString,
|
||||
@@ -90,5 +72,34 @@ export function createChart(
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -6,10 +6,17 @@ type HistogramOptions = DeepPartial<
|
||||
|
||||
export const PRICE_SCALE_MOMENTUM_ID = "momentum";
|
||||
|
||||
export const createHistogramSeries = (
|
||||
chart: IChartApi,
|
||||
options?: HistogramOptions,
|
||||
) => {
|
||||
export const createHistogramSeries = ({
|
||||
chart,
|
||||
// dark,
|
||||
// color,
|
||||
options,
|
||||
}: {
|
||||
chart: IChartApi;
|
||||
// dark: Accessor<boolean>;
|
||||
// color: Color;
|
||||
options?: HistogramOptions;
|
||||
}) => {
|
||||
const seriesOptions: HistogramOptions = {
|
||||
priceScaleId: "left",
|
||||
...defaultSeriesOptions,
|
||||
|
||||
@@ -9,6 +9,7 @@ export class HorzScaleBehaviorHeight implements IHorzScaleBehavior<number> {
|
||||
setOptions() {}
|
||||
preprocessData() {}
|
||||
updateFormatter() {}
|
||||
|
||||
createConverterToInternalObj() {
|
||||
return (price) => price;
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ export function createSeriesLegend<Scale extends ResourceScale>({
|
||||
id: string;
|
||||
presetId: string;
|
||||
title: string;
|
||||
color: Accessor<string | string[]>;
|
||||
color: Color | Color[];
|
||||
seriesList: Accessor<ISeriesApi<SeriesType> | undefined>[];
|
||||
defaultVisible?: boolean;
|
||||
disabled?: Accessor<boolean>;
|
||||
|
||||
@@ -1,10 +1,26 @@
|
||||
import { defaultSeriesOptions } from "./options";
|
||||
|
||||
export const createLineSeries = (
|
||||
chart: IChartApi,
|
||||
options?: DeepPartialLineOptions,
|
||||
) =>
|
||||
chart.addLineSeries({
|
||||
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;
|
||||
};
|
||||
|
||||
@@ -6,12 +6,12 @@ export function setMinMaxMarkers({
|
||||
scale,
|
||||
visibleRange,
|
||||
legendList,
|
||||
activeRange,
|
||||
activeIds,
|
||||
}: {
|
||||
scale: ResourceScale;
|
||||
visibleRange: TimeRange | undefined;
|
||||
legendList: SeriesLegend[];
|
||||
activeRange: Accessor<number[]>;
|
||||
activeIds: Accessor<number[]>;
|
||||
}) {
|
||||
if (!visibleRange) return;
|
||||
|
||||
@@ -24,7 +24,7 @@ export function setMinMaxMarkers({
|
||||
let min = undefined as [number, Time, number, ISeriesApi<any>] | undefined;
|
||||
|
||||
legendList.forEach(({ seriesList, dataset }) => {
|
||||
activeRange().forEach((id) => {
|
||||
activeIds().forEach((id) => {
|
||||
const seriesIndex = chunkIdToIndex(scale, id);
|
||||
|
||||
const series = seriesList.at(seriesIndex)?.();
|
||||
|
||||
@@ -1,48 +1,13 @@
|
||||
import { HEIGHT_CHUNK_SIZE } from "../datasets";
|
||||
import { debounce } from "../utils/debounce";
|
||||
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 initTimeScale({
|
||||
scale,
|
||||
activeRange,
|
||||
exactRange,
|
||||
charts,
|
||||
}: {
|
||||
scale: ResourceScale;
|
||||
activeRange: RWS<number[]>;
|
||||
exactRange: RWS<TimeRange | undefined>;
|
||||
charts: ChartObject[];
|
||||
}) {
|
||||
const firstChart = charts.at(0)?.chart;
|
||||
|
||||
if (!firstChart) return;
|
||||
|
||||
firstChart.timeScale().subscribeVisibleTimeRangeChange((range) => {
|
||||
if (!range) return;
|
||||
|
||||
exactRange.set(range);
|
||||
|
||||
debouncedSetActiveRange({ range, activeRange });
|
||||
|
||||
debouncedSaveTimeRange({ scale, range });
|
||||
});
|
||||
|
||||
setTimeScale(firstChart, getInitialRange(scale));
|
||||
}
|
||||
|
||||
function setTimeScale(chart: IChartApi, range: TimeRange | null) {
|
||||
if (range) {
|
||||
setTimeout(() => {
|
||||
chart.timeScale().setVisibleRange(range);
|
||||
}, 1);
|
||||
}
|
||||
}
|
||||
|
||||
function getInitialRange(scale: ResourceScale): TimeRange {
|
||||
export function getInitialTimeRange(scale: ResourceScale): TimeRange {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
|
||||
const urlFrom = urlParams.get(URL_PARAMS_RANGE_FROM_KEY);
|
||||
@@ -94,38 +59,76 @@ function getInitialRange(scale: ResourceScale): 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 });
|
||||
});
|
||||
|
||||
setTimeScale(firstChart, exactRange());
|
||||
}
|
||||
|
||||
async function setTimeScale(chart: IChartApi, range: TimeRange | null) {
|
||||
if (range) {
|
||||
await tick();
|
||||
chart.timeScale().setVisibleRange(range);
|
||||
}
|
||||
}
|
||||
|
||||
function getLocalStorageKey(scale: ResourceScale) {
|
||||
return `${LOCAL_STORAGE_RANGE_KEY}-${scale}`;
|
||||
}
|
||||
|
||||
function setActiveRange({
|
||||
range,
|
||||
activeRange,
|
||||
export function setActiveIds({
|
||||
exactRange,
|
||||
activeIds,
|
||||
}: {
|
||||
range: TimeRange;
|
||||
activeRange: RWS<number[]>;
|
||||
exactRange: TimeRange;
|
||||
activeIds: RWS<number[]>;
|
||||
}) {
|
||||
let ids: number[] = [];
|
||||
|
||||
const today = new Date();
|
||||
|
||||
if (typeof range.from === "string" && typeof range.to === "string") {
|
||||
const from = new Date(range.from).getUTCFullYear();
|
||||
const to = new Date(range.to).getUTCFullYear();
|
||||
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(range.from) / HEIGHT_CHUNK_SIZE);
|
||||
const to = Math.floor(Number(range.to) / HEIGHT_CHUNK_SIZE);
|
||||
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 = activeRange();
|
||||
const old = activeIds();
|
||||
|
||||
if (
|
||||
old.length !== ids.length ||
|
||||
@@ -134,11 +137,11 @@ function setActiveRange({
|
||||
) {
|
||||
console.log("range:", ids);
|
||||
|
||||
activeRange.set(ids);
|
||||
activeIds.set(ids);
|
||||
}
|
||||
}
|
||||
|
||||
const debouncedSetActiveRange = debounce(setActiveRange, 100);
|
||||
const debouncedsetActiveIds = debounce(setActiveIds, 100);
|
||||
|
||||
function saveTimeRange({
|
||||
scale,
|
||||
|
||||
+1
-3
@@ -19,6 +19,4 @@ interface BaselineSeriesOptions {
|
||||
title?: string;
|
||||
}
|
||||
|
||||
type SeriesLegend = ReturnType<
|
||||
typeof import("../../chart/legend").createSeriesLegend
|
||||
>;
|
||||
type SeriesLegend = ReturnType<typeof import("./legend").createSeriesLegend>;
|
||||
|
||||
@@ -4,9 +4,9 @@ import { createLineSeries } from "./line";
|
||||
export const GENESIS_DAY = "2009-01-03";
|
||||
|
||||
const whitespaceStartDate = new Date("1970-01-01");
|
||||
const whitespaceStartDateYear = whitespaceStartDate.getFullYear();
|
||||
const whitespaceStartDateMonth = whitespaceStartDate.getMonth();
|
||||
const whitespaceStartDateDate = whitespaceStartDate.getDate();
|
||||
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),
|
||||
@@ -47,7 +47,7 @@ for (let i = 0; i < whitespaceHeightDataset.length; i++) {
|
||||
}
|
||||
|
||||
export function setWhitespace(chart: IChartApi, scale: ResourceScale) {
|
||||
const whitespace = createLineSeries(chart);
|
||||
const whitespace = chart.addLineSeries();
|
||||
|
||||
if (scale === "date") {
|
||||
whitespace.setData(whitespaceDateDataset);
|
||||
@@ -63,3 +63,81 @@ export function setWhitespace(chart: IChartApi, scale: ResourceScale) {
|
||||
|
||||
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,
|
||||
// };
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
@@ -127,7 +127,7 @@ function createAddressPresetFolder<Scale extends ResourceScale>({
|
||||
scale: Scale;
|
||||
name: string;
|
||||
datasetKey: AddressCohortKey;
|
||||
color: string;
|
||||
color: Color;
|
||||
}): PartialPresetFolder {
|
||||
return {
|
||||
name,
|
||||
@@ -159,7 +159,7 @@ export function createLiquidityFolder<Scale extends ResourceScale>({
|
||||
scale: Scale;
|
||||
name: string;
|
||||
datasetKey: AddressCohortKey | "";
|
||||
color: string;
|
||||
color: Color;
|
||||
}): PartialPresetFolder {
|
||||
return {
|
||||
name: `Split By Liquidity`,
|
||||
@@ -189,7 +189,7 @@ export function createAddressCountPreset<Scale extends ResourceScale>({
|
||||
scale: Scale;
|
||||
name: string;
|
||||
datasetKey: AddressCohortKey;
|
||||
color: string;
|
||||
color: Color;
|
||||
}): PartialPreset {
|
||||
return {
|
||||
scale,
|
||||
|
||||
@@ -11,7 +11,11 @@ import { createHistogramSeries } from "../lightweightCharts/histogram";
|
||||
import { createSeriesLegend } from "../lightweightCharts/legend";
|
||||
import { createLineSeries } from "../lightweightCharts/line";
|
||||
import { setMinMaxMarkers } from "../lightweightCharts/markers";
|
||||
import { initTimeScale } from "../lightweightCharts/time";
|
||||
import {
|
||||
getInitialTimeRange,
|
||||
initTimeScale,
|
||||
setActiveIds,
|
||||
} from "../lightweightCharts/time";
|
||||
import { setWhitespace } from "../lightweightCharts/whitespace";
|
||||
import { colors } from "../utils/colors";
|
||||
import { debounce } from "../utils/debounce";
|
||||
@@ -27,7 +31,9 @@ export enum SeriesType {
|
||||
type SeriesConfig<Scale extends ResourceScale> =
|
||||
| {
|
||||
dataset: ResourceDataset<Scale>;
|
||||
color?: string;
|
||||
color?: Color;
|
||||
topColor?: Color;
|
||||
bottomColor?: Color;
|
||||
colors?: undefined;
|
||||
seriesType: SeriesType.Based;
|
||||
title: string;
|
||||
@@ -37,8 +43,8 @@ type SeriesConfig<Scale extends ResourceScale> =
|
||||
}
|
||||
| {
|
||||
dataset: ResourceDataset<Scale>;
|
||||
color?: string;
|
||||
colors?: string[];
|
||||
color?: Color;
|
||||
colors?: Color[];
|
||||
seriesType: SeriesType.Histogram;
|
||||
title: string;
|
||||
options?: DeepPartialHistogramOptions;
|
||||
@@ -57,7 +63,7 @@ type SeriesConfig<Scale extends ResourceScale> =
|
||||
}
|
||||
| {
|
||||
dataset: ResourceDataset<Scale>;
|
||||
color: string;
|
||||
color: Color;
|
||||
colors?: undefined;
|
||||
seriesType?: SeriesType.Line;
|
||||
title: string;
|
||||
@@ -78,7 +84,7 @@ export function applySeriesList<Scale extends ResourceScale>({
|
||||
priceOptions,
|
||||
legendSetter,
|
||||
dark,
|
||||
activeRange,
|
||||
activeIds,
|
||||
}: {
|
||||
charts: RWS<IChartApi[]>;
|
||||
parentDiv: HTMLDivElement;
|
||||
@@ -90,8 +96,8 @@ export function applySeriesList<Scale extends ResourceScale>({
|
||||
top?: SeriesConfig<Scale>[];
|
||||
bottom?: SeriesConfig<Scale>[];
|
||||
datasets: Datasets;
|
||||
dark: boolean;
|
||||
activeRange: RWS<number[]>;
|
||||
dark: Accessor<boolean>;
|
||||
activeIds: RWS<number[]>;
|
||||
}) {
|
||||
// ---
|
||||
// Reset states
|
||||
@@ -107,8 +113,6 @@ export function applySeriesList<Scale extends ResourceScale>({
|
||||
return [];
|
||||
});
|
||||
|
||||
activeRange.set([]);
|
||||
|
||||
parentDiv.replaceChildren();
|
||||
|
||||
// ---
|
||||
@@ -124,11 +128,18 @@ export function applySeriesList<Scale extends ResourceScale>({
|
||||
const activeDatasets: ResourceDataset<any, any>[] = [];
|
||||
|
||||
const lastActiveIndex = createMemo(() => {
|
||||
const last = activeRange().at(-1);
|
||||
const last = activeIds().at(-1);
|
||||
return last !== undefined ? chunkIdToIndex(scale, last) : undefined;
|
||||
});
|
||||
|
||||
const exactRange = createRWS(undefined as TimeRange | 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] : []))
|
||||
@@ -157,16 +168,74 @@ export function applySeriesList<Scale extends ResourceScale>({
|
||||
|
||||
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 debouncedSetMinMaxMarkers = debounce(() => {
|
||||
function _setMinMaxMarkers() {
|
||||
setMinMaxMarkers({
|
||||
scale,
|
||||
visibleRange: exactRange(),
|
||||
legendList: chartLegend,
|
||||
activeRange,
|
||||
activeIds: activeIds,
|
||||
});
|
||||
}, 50);
|
||||
}
|
||||
|
||||
const debouncedSetMinMaxMarkers = debounce(
|
||||
_setMinMaxMarkers,
|
||||
seriesNumber * 10,
|
||||
);
|
||||
|
||||
createEffect(on(exactRange, debouncedSetMinMaxMarkers));
|
||||
|
||||
if (index === 0) {
|
||||
const dataset =
|
||||
@@ -206,7 +275,7 @@ export function applySeriesList<Scale extends ResourceScale>({
|
||||
}
|
||||
|
||||
return createSeriesGroup({
|
||||
activeRange,
|
||||
activeIds,
|
||||
seriesConfig,
|
||||
chart,
|
||||
chartLegend,
|
||||
@@ -214,6 +283,7 @@ export function applySeriesList<Scale extends ResourceScale>({
|
||||
preset,
|
||||
disabled: () => priceSeriesType() !== seriesType,
|
||||
debouncedSetMinMaxMarkers,
|
||||
dark,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -233,13 +303,14 @@ export function applySeriesList<Scale extends ResourceScale>({
|
||||
activeDatasets.push(seriesConfig.dataset);
|
||||
|
||||
createSeriesGroup({
|
||||
activeRange,
|
||||
activeIds: activeIds,
|
||||
seriesConfig,
|
||||
chartLegend,
|
||||
chart,
|
||||
preset,
|
||||
lastActiveIndex,
|
||||
debouncedSetMinMaxMarkers,
|
||||
dark,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -249,8 +320,6 @@ export function applySeriesList<Scale extends ResourceScale>({
|
||||
createEffect(on(legend.visible, debouncedSetMinMaxMarkers));
|
||||
});
|
||||
|
||||
createEffect(on(exactRange, debouncedSetMinMaxMarkers));
|
||||
|
||||
return [
|
||||
{
|
||||
scale,
|
||||
@@ -271,7 +340,8 @@ export function applySeriesList<Scale extends ResourceScale>({
|
||||
chart.div.style.border = "";
|
||||
visibleCharts.push(chart);
|
||||
} else {
|
||||
chart.div.style.height = "0px";
|
||||
chart.div.style.height = "100%";
|
||||
// chart.div.style.height = "0px";
|
||||
chart.div.style.border = "none";
|
||||
}
|
||||
});
|
||||
@@ -296,13 +366,13 @@ export function applySeriesList<Scale extends ResourceScale>({
|
||||
initTimeScale({
|
||||
scale,
|
||||
charts,
|
||||
activeRange,
|
||||
activeIds: activeIds,
|
||||
exactRange,
|
||||
});
|
||||
|
||||
const activeDatasetsLength = activeDatasets.length;
|
||||
createEffect(() => {
|
||||
const range = activeRange();
|
||||
const range = activeIds();
|
||||
|
||||
untrack(() => {
|
||||
for (let i = 0; i < range.length; i++) {
|
||||
@@ -382,7 +452,7 @@ function updateVisiblePriceSeriesType(
|
||||
}
|
||||
|
||||
function createSeriesGroup<Scale extends ResourceScale>({
|
||||
activeRange,
|
||||
activeIds,
|
||||
seriesConfig,
|
||||
preset,
|
||||
chartLegend,
|
||||
@@ -390,8 +460,9 @@ function createSeriesGroup<Scale extends ResourceScale>({
|
||||
disabled,
|
||||
lastActiveIndex,
|
||||
debouncedSetMinMaxMarkers,
|
||||
dark,
|
||||
}: {
|
||||
activeRange: Accessor<number[]>;
|
||||
activeIds: Accessor<number[]>;
|
||||
seriesConfig: SeriesConfig<Scale>;
|
||||
preset: Preset;
|
||||
chart: IChartApi;
|
||||
@@ -399,6 +470,7 @@ function createSeriesGroup<Scale extends ResourceScale>({
|
||||
lastActiveIndex: Accessor<number | undefined>;
|
||||
disabled?: Accessor<boolean>;
|
||||
debouncedSetMinMaxMarkers: VoidFunction;
|
||||
dark: Accessor<boolean>;
|
||||
}) {
|
||||
const {
|
||||
dataset,
|
||||
@@ -417,15 +489,12 @@ function createSeriesGroup<Scale extends ResourceScale>({
|
||||
ISeriesApi<"Baseline" | "Line" | "Histogram" | "Candlestick"> | undefined
|
||||
>[] = new Array(dataset.fetchedJSONs.length);
|
||||
|
||||
let defaultSeriesColor: string | string[] | undefined = undefined;
|
||||
|
||||
const legend = createSeriesLegend({
|
||||
id: stringToId(title),
|
||||
presetId: preset.id,
|
||||
title,
|
||||
seriesList,
|
||||
color: () =>
|
||||
colors || color || defaultSeriesColor || DEFAULT_BASELINE_COLORS,
|
||||
color: colors || color || DEFAULT_BASELINE_COLORS,
|
||||
defaultVisible,
|
||||
disabled,
|
||||
dataset,
|
||||
@@ -448,37 +517,47 @@ function createSeriesGroup<Scale extends ResourceScale>({
|
||||
if (!s) {
|
||||
switch (type) {
|
||||
case SeriesType.Based: {
|
||||
s = createBaseLineSeries(chart, {
|
||||
s = createBaseLineSeries({
|
||||
chart,
|
||||
dark,
|
||||
color,
|
||||
...options,
|
||||
topColor: seriesConfig.topColor,
|
||||
bottomColor: seriesConfig.bottomColor,
|
||||
options,
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
case SeriesType.Candlestick: {
|
||||
const candlestickSeries = createCandlesticksSeries(
|
||||
const candlestickSeries = createCandlesticksSeries({
|
||||
chart,
|
||||
options,
|
||||
);
|
||||
dark,
|
||||
});
|
||||
|
||||
s = candlestickSeries[0];
|
||||
defaultSeriesColor = candlestickSeries[1];
|
||||
|
||||
if (!colors && !color) {
|
||||
legend.color = candlestickSeries[1];
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case SeriesType.Histogram: {
|
||||
s = createHistogramSeries(chart, {
|
||||
color,
|
||||
...options,
|
||||
s = createHistogramSeries({
|
||||
chart,
|
||||
options,
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
case SeriesType.Line: {
|
||||
s = createLineSeries(chart, {
|
||||
s = createLineSeries({
|
||||
chart,
|
||||
color,
|
||||
...options,
|
||||
dark,
|
||||
options,
|
||||
});
|
||||
|
||||
break;
|
||||
@@ -494,9 +573,7 @@ function createSeriesGroup<Scale extends ResourceScale>({
|
||||
|
||||
s.setData(values);
|
||||
|
||||
untrack(() => {
|
||||
debouncedSetMinMaxMarkers();
|
||||
});
|
||||
debouncedSetMinMaxMarkers();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -522,7 +599,7 @@ function createSeriesGroup<Scale extends ResourceScale>({
|
||||
});
|
||||
|
||||
const inRange = createMemo(() => {
|
||||
const range = activeRange();
|
||||
const range = activeIds();
|
||||
|
||||
if (range.length) {
|
||||
const start = chunkIdToIndex(scale, range.at(0)!);
|
||||
|
||||
@@ -540,7 +540,7 @@ export function createPresets<Scale extends ResourceScale>({
|
||||
bottom: [
|
||||
{
|
||||
title: "Concurrent Liveliness 14d Median",
|
||||
color: `${colors.liveliness}66`,
|
||||
color: colors.darkLiveliness,
|
||||
dataset:
|
||||
params.datasets[scale].concurrent_liveliness_2w_median,
|
||||
},
|
||||
@@ -732,13 +732,13 @@ export function createPresets<Scale extends ResourceScale>({
|
||||
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,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { phone } from "/src/env";
|
||||
import { createRWS } from "/src/solid/rws";
|
||||
|
||||
import { colors } from "../utils/colors";
|
||||
@@ -32,7 +33,7 @@ export function createPresets(): Presets {
|
||||
name: "Charts",
|
||||
tree: [
|
||||
{
|
||||
name: "By Date",
|
||||
name: "By Block Date",
|
||||
tree: [
|
||||
createMarketPresets("date"),
|
||||
createBlocksPresets(),
|
||||
@@ -57,28 +58,30 @@ export function createPresets(): Presets {
|
||||
],
|
||||
} satisfies PartialPresetFolder,
|
||||
{
|
||||
name: "By Height",
|
||||
tree: [
|
||||
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" }),
|
||||
],
|
||||
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,
|
||||
],
|
||||
},
|
||||
@@ -263,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 &&
|
||||
|
||||
@@ -45,7 +45,7 @@ function createPresetFolder({
|
||||
key,
|
||||
}: {
|
||||
scale: ResourceScale;
|
||||
color: string;
|
||||
color: Color;
|
||||
name: string;
|
||||
key: AverageName;
|
||||
}) {
|
||||
|
||||
@@ -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)
|
||||
|
||||
Vendored
+2
-2
@@ -25,8 +25,8 @@ type ApplyPreset = (params: {
|
||||
datasets: Datasets;
|
||||
preset: Preset;
|
||||
legendSetter: Setter<SeriesLegend[]>;
|
||||
dark: boolean;
|
||||
activeRange: RWS<number[]>;
|
||||
dark: Accessor<boolean>;
|
||||
activeIds: RWS<number[]>;
|
||||
}) => void;
|
||||
|
||||
interface PartialPresetFolder {
|
||||
|
||||
+121
-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 },
|
||||
|
||||
@@ -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.getTime() - oldest.getTime()) / ONE_DAY_IN_MS));
|
||||
|
||||
@@ -3,7 +3,3 @@ export function sleep(ms: number) {
|
||||
setTimeout(resolve, ms);
|
||||
});
|
||||
}
|
||||
|
||||
export function tick() {
|
||||
return sleep(1);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { sleep } from "./sleep";
|
||||
|
||||
export function tick() {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(resolve, 0);
|
||||
});
|
||||
return sleep(0);
|
||||
}
|
||||
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
type Color = (dark: Accessor<boolean>) => string;
|
||||
@@ -35,6 +35,10 @@ mark {
|
||||
@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;
|
||||
|
||||
Reference in New Issue
Block a user