general: snapshot

This commit is contained in:
k
2024-07-08 17:31:51 +02:00
parent 80ea12ed48
commit 04359fbf31
37 changed files with 787 additions and 322 deletions
+4 -1
View File
@@ -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
+4 -2
View File
@@ -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_:
+1
View File
@@ -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";
+11 -4
View File
@@ -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">
+145 -12
View File
@@ -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
View File
@@ -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");
+16
View File
@@ -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,
);
-28
View File
@@ -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;
};
+35 -27
View File
@@ -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]];
};
+30 -19
View File
@@ -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;
}
+11 -4
View File
@@ -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;
}
+1 -1
View File
@@ -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>;
+21 -5
View File
@@ -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;
};
+3 -3
View File
@@ -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)?.();
+53 -50
View File
@@ -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
View File
@@ -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,
// };
// }
// }
// }
+3 -3
View File
@@ -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,
+119 -42
View File
@@ -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)!);
+3 -3
View File
@@ -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,
+34 -24
View File
@@ -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;
}) {
+2 -2
View File
@@ -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)
+2 -2
View File
@@ -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
View File
@@ -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 },
-4
View File
@@ -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));
-4
View File
@@ -3,7 +3,3 @@ export function sleep(ms: number) {
setTimeout(resolve, ms);
});
}
export function tick() {
return sleep(1);
}
+3 -3
View File
@@ -1,5 +1,5 @@
import { sleep } from "./sleep";
export function tick() {
return new Promise((resolve) => {
setTimeout(resolve, 0);
});
return sleep(0);
}
+1
View File
@@ -0,0 +1 @@
type Color = (dark: Accessor<boolean>) => string;
+4
View File
@@ -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;