diff --git a/CHANGELOG.md b/CHANGELOG.md index d34c01262..a9b3e8af9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/README.md b/README.md index 3cc8024ac..39ce349be 100644 --- a/README.md +++ b/README.md @@ -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_: diff --git a/app/src/app/components/background.tsx b/app/src/app/components/background.tsx index 936aa7ea4..263c854c9 100644 --- a/app/src/app/components/background.tsx +++ b/app/src/app/components/background.tsx @@ -38,6 +38,7 @@ const texts = [ "nyknyc", "low time preference", "absolute scarcity", + "time is scarce", ]; export function Background({ diff --git a/app/src/app/components/frames/chart/components/chart.tsx b/app/src/app/components/frames/chart/components/chart.tsx index 09a11aee7..84bee5390 100644 --- a/app/src/app/components/frames/chart/components/chart.tsx +++ b/app/src/app/components/frames/chart/components/chart.tsx @@ -6,8 +6,8 @@ export function Chart({ presets, datasets, legendSetter, - dark: _dark, - activeRange, + dark, + activeIds, }: { charts: RWS; parentDiv: RWS; @@ -15,7 +15,7 @@ export function Chart({ datasets: Datasets; legendSetter: Setter; dark: Accessor; - activeRange: RWS; + activeIds: RWS; }) { 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); diff --git a/app/src/app/components/frames/chart/components/legend.tsx b/app/src/app/components/frames/chart/components/legend.tsx index 115ef4740..b8c8f9bb3 100644 --- a/app/src/app/components/frames/chart/components/legend.tsx +++ b/app/src/app/components/frames/chart/components/legend.tsx @@ -8,11 +8,13 @@ const transparency = "44"; export function Legend({ scale, legend: legendList, - activeRange, + dark, + activeIds, }: { scale: Accessor; legend: Accessor; - activeRange: Accessor; + dark: Accessor; + activeIds: Accessor; }) { const hovered = createRWS(undefined); @@ -23,7 +25,7 @@ export function Legend({ {(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({ > c(dark)) + : [legend.color(dark)] } > {(color) => ( diff --git a/app/src/app/components/frames/chart/index.tsx b/app/src/app/components/frames/chart/index.tsx index 274c1dac3..5a2ee0e04 100644 --- a/app/src/app/components/frames/chart/index.tsx +++ b/app/src/app/components/frames/chart/index.tsx @@ -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({
- +
@@ -71,7 +76,7 @@ export function ChartFrame({ legendSetter={legend.set} presets={presets} dark={dark} - activeRange={activeRange} + activeIds={activeIds} />
diff --git a/app/src/app/components/frames/folders/index.tsx b/app/src/app/components/frames/folders/index.tsx index 23032637f..95aac8989 100644 --- a/app/src/app/components/frames/folders/index.tsx +++ b/app/src/app/components/frames/folders/index.tsx @@ -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"; diff --git a/app/src/app/components/frames/search.tsx b/app/src/app/components/frames/search.tsx index 04d9b4bc3..50469af70 100644 --- a/app/src/app/components/frames/search.tsx +++ b/app/src/app/components/frames/search.tsx @@ -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)} /> diff --git a/app/src/app/components/frames/settings.tsx b/app/src/app/components/frames/settings.tsx index 9bba0f2b7..9282c4356 100644 --- a/app/src/app/components/frames/settings.tsx +++ b/app/src/app/components/frames/settings.tsx @@ -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({
-

General

+ General
-

Background

+ Background
-

- Version:{" "} - - {version} - -

+ +
+ Donations + +

+ A massive thank you 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. +

+

Top 10 Leaderboard:

+
    + + b.amount !== a.amount + ? b.amount - a.amount + : a.name.localeCompare(b.name), + ) + .slice(0, 10)} + > + {({ name, url, amount }, index) => ( +
  1. + + {name} + {" "} + - {amount.toLocaleString("en-us")} sats +
  2. + )} +
    +
+
+ + +
+ +
+ Install +

+ + This app can be installed by clicking on the "File" tab on the + menu bar and then on "Add to dock". + + + This app can be installed by tapping on the "Share" button tab + of Safari and then on "Add to Home Screen". + +

+
+
+ +
+ +
+
+ + + + +
+
+ +

+ Version:{" "} + + {version} + +

); } +function Title({ children }: ParentProps) { + return

{children}

; +} + 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", ])} > ( + 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"); diff --git a/app/src/env.ts b/app/src/env.ts index f3faa4534..8ec1956cb 100644 --- a/app/src/env.ts +++ b/app/src/env.ts @@ -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, + ); diff --git a/app/src/scripts/lightweightCharts/area.ts b/app/src/scripts/lightweightCharts/area.ts deleted file mode 100644 index 8aa2919c1..000000000 --- a/app/src/scripts/lightweightCharts/area.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { defaultSeriesOptions } from "./options"; - -type AreaOptions = DeepPartial; - -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; -}; diff --git a/app/src/scripts/lightweightCharts/baseLine.ts b/app/src/scripts/lightweightCharts/baseLine.ts index 0ca94d351..f3bbbb85b 100644 --- a/app/src/scripts/lightweightCharts/baseLine.ts +++ b/app/src/scripts/lightweightCharts/baseLine.ts @@ -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; + 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; }; diff --git a/app/src/scripts/lightweightCharts/candlesticks.ts b/app/src/scripts/lightweightCharts/candlesticks.ts index 06374fe18..836189e6f 100644 --- a/app/src/scripts/lightweightCharts/candlesticks.ts +++ b/app/src/scripts/lightweightCharts/candlesticks.ts @@ -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; + 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]]; }; diff --git a/app/src/scripts/lightweightCharts/create.ts b/app/src/scripts/lightweightCharts/create.ts index 51aabdce6..4b405b118 100644 --- a/app/src/scripts/lightweightCharts/create.ts +++ b/app/src/scripts/lightweightCharts/create.ts @@ -15,34 +15,24 @@ export function createChart( dark, priceScaleOptions, }: { - dark: boolean; + dark: Accessor; 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; } diff --git a/app/src/scripts/lightweightCharts/histogram.ts b/app/src/scripts/lightweightCharts/histogram.ts index 66232c108..4833936e7 100644 --- a/app/src/scripts/lightweightCharts/histogram.ts +++ b/app/src/scripts/lightweightCharts/histogram.ts @@ -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; + // color: Color; + options?: HistogramOptions; +}) => { const seriesOptions: HistogramOptions = { priceScaleId: "left", ...defaultSeriesOptions, diff --git a/app/src/scripts/lightweightCharts/horzScaleBehavior.ts b/app/src/scripts/lightweightCharts/horzScaleBehavior.ts index 68c9178f1..aeeb22e84 100644 --- a/app/src/scripts/lightweightCharts/horzScaleBehavior.ts +++ b/app/src/scripts/lightweightCharts/horzScaleBehavior.ts @@ -9,6 +9,7 @@ export class HorzScaleBehaviorHeight implements IHorzScaleBehavior { setOptions() {} preprocessData() {} updateFormatter() {} + createConverterToInternalObj() { return (price) => price; } diff --git a/app/src/scripts/lightweightCharts/legend.ts b/app/src/scripts/lightweightCharts/legend.ts index 03be55b87..8828df95a 100644 --- a/app/src/scripts/lightweightCharts/legend.ts +++ b/app/src/scripts/lightweightCharts/legend.ts @@ -22,7 +22,7 @@ export function createSeriesLegend({ id: string; presetId: string; title: string; - color: Accessor; + color: Color | Color[]; seriesList: Accessor | undefined>[]; defaultVisible?: boolean; disabled?: Accessor; diff --git a/app/src/scripts/lightweightCharts/line.ts b/app/src/scripts/lightweightCharts/line.ts index 90dd5cd08..149e523d6 100644 --- a/app/src/scripts/lightweightCharts/line.ts +++ b/app/src/scripts/lightweightCharts/line.ts @@ -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; + color: Color; + options?: DeepPartialLineOptions; +}) => { + const series = chart.addLineSeries({ ...defaultSeriesOptions, ...options, }); + + createEffect(() => { + series.applyOptions({ + color: color(dark), + }); + }); + + return series; +}; diff --git a/app/src/scripts/lightweightCharts/markers.ts b/app/src/scripts/lightweightCharts/markers.ts index 2061dfa1c..570abc407 100644 --- a/app/src/scripts/lightweightCharts/markers.ts +++ b/app/src/scripts/lightweightCharts/markers.ts @@ -6,12 +6,12 @@ export function setMinMaxMarkers({ scale, visibleRange, legendList, - activeRange, + activeIds, }: { scale: ResourceScale; visibleRange: TimeRange | undefined; legendList: SeriesLegend[]; - activeRange: Accessor; + activeIds: Accessor; }) { if (!visibleRange) return; @@ -24,7 +24,7 @@ export function setMinMaxMarkers({ let min = undefined as [number, Time, number, ISeriesApi] | undefined; legendList.forEach(({ seriesList, dataset }) => { - activeRange().forEach((id) => { + activeIds().forEach((id) => { const seriesIndex = chunkIdToIndex(scale, id); const series = seriesList.at(seriesIndex)?.(); diff --git a/app/src/scripts/lightweightCharts/time.ts b/app/src/scripts/lightweightCharts/time.ts index fd6bff2a1..b98b384da 100644 --- a/app/src/scripts/lightweightCharts/time.ts +++ b/app/src/scripts/lightweightCharts/time.ts @@ -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; - exactRange: RWS; - 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; + exactRange: RWS; + 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; + exactRange: TimeRange; + activeIds: RWS; }) { 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, diff --git a/app/src/scripts/lightweightCharts/types.d.ts b/app/src/scripts/lightweightCharts/types.d.ts index 38581c49f..d2c991263 100644 --- a/app/src/scripts/lightweightCharts/types.d.ts +++ b/app/src/scripts/lightweightCharts/types.d.ts @@ -19,6 +19,4 @@ interface BaselineSeriesOptions { title?: string; } -type SeriesLegend = ReturnType< - typeof import("../../chart/legend").createSeriesLegend ->; +type SeriesLegend = ReturnType; diff --git a/app/src/scripts/lightweightCharts/whitespace.ts b/app/src/scripts/lightweightCharts/whitespace.ts index e90104157..15e373ed2 100644 --- a/app/src/scripts/lightweightCharts/whitespace.ts +++ b/app/src/scripts/lightweightCharts/whitespace.ts @@ -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, +// }; +// } +// } +// } diff --git a/app/src/scripts/presets/addresses/index.ts b/app/src/scripts/presets/addresses/index.ts index 978c12c23..d0c6d25ac 100644 --- a/app/src/scripts/presets/addresses/index.ts +++ b/app/src/scripts/presets/addresses/index.ts @@ -127,7 +127,7 @@ function createAddressPresetFolder({ scale: Scale; name: string; datasetKey: AddressCohortKey; - color: string; + color: Color; }): PartialPresetFolder { return { name, @@ -159,7 +159,7 @@ export function createLiquidityFolder({ scale: Scale; name: string; datasetKey: AddressCohortKey | ""; - color: string; + color: Color; }): PartialPresetFolder { return { name: `Split By Liquidity`, @@ -189,7 +189,7 @@ export function createAddressCountPreset({ scale: Scale; name: string; datasetKey: AddressCohortKey; - color: string; + color: Color; }): PartialPreset { return { scale, diff --git a/app/src/scripts/presets/apply.ts b/app/src/scripts/presets/apply.ts index ee5246879..1fd19afcf 100644 --- a/app/src/scripts/presets/apply.ts +++ b/app/src/scripts/presets/apply.ts @@ -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 = | { dataset: ResourceDataset; - color?: string; + color?: Color; + topColor?: Color; + bottomColor?: Color; colors?: undefined; seriesType: SeriesType.Based; title: string; @@ -37,8 +43,8 @@ type SeriesConfig = } | { dataset: ResourceDataset; - color?: string; - colors?: string[]; + color?: Color; + colors?: Color[]; seriesType: SeriesType.Histogram; title: string; options?: DeepPartialHistogramOptions; @@ -57,7 +63,7 @@ type SeriesConfig = } | { dataset: ResourceDataset; - color: string; + color: Color; colors?: undefined; seriesType?: SeriesType.Line; title: string; @@ -78,7 +84,7 @@ export function applySeriesList({ priceOptions, legendSetter, dark, - activeRange, + activeIds, }: { charts: RWS; parentDiv: HTMLDivElement; @@ -90,8 +96,8 @@ export function applySeriesList({ top?: SeriesConfig[]; bottom?: SeriesConfig[]; datasets: Datasets; - dark: boolean; - activeRange: RWS; + dark: Accessor; + activeIds: RWS; }) { // --- // Reset states @@ -107,8 +113,6 @@ export function applySeriesList({ return []; }); - activeRange.set([]); - parentDiv.replaceChildren(); // --- @@ -124,11 +128,18 @@ export function applySeriesList({ const activeDatasets: ResourceDataset[] = []; 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({ const whitespace = setWhitespace(chart, scale); + if (exactRange()) { + chart.timeScale().setVisibleRange(exactRange()); + } + + // const whitespace = new Array | 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({ } return createSeriesGroup({ - activeRange, + activeIds, seriesConfig, chart, chartLegend, @@ -214,6 +283,7 @@ export function applySeriesList({ preset, disabled: () => priceSeriesType() !== seriesType, debouncedSetMinMaxMarkers, + dark, }); } @@ -233,13 +303,14 @@ export function applySeriesList({ activeDatasets.push(seriesConfig.dataset); createSeriesGroup({ - activeRange, + activeIds: activeIds, seriesConfig, chartLegend, chart, preset, lastActiveIndex, debouncedSetMinMaxMarkers, + dark, }); }); @@ -249,8 +320,6 @@ export function applySeriesList({ createEffect(on(legend.visible, debouncedSetMinMaxMarkers)); }); - createEffect(on(exactRange, debouncedSetMinMaxMarkers)); - return [ { scale, @@ -271,7 +340,8 @@ export function applySeriesList({ 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({ 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({ - activeRange, + activeIds, seriesConfig, preset, chartLegend, @@ -390,8 +460,9 @@ function createSeriesGroup({ disabled, lastActiveIndex, debouncedSetMinMaxMarkers, + dark, }: { - activeRange: Accessor; + activeIds: Accessor; seriesConfig: SeriesConfig; preset: Preset; chart: IChartApi; @@ -399,6 +470,7 @@ function createSeriesGroup({ lastActiveIndex: Accessor; disabled?: Accessor; debouncedSetMinMaxMarkers: VoidFunction; + dark: Accessor; }) { const { dataset, @@ -417,15 +489,12 @@ function createSeriesGroup({ 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({ 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({ s.setData(values); - untrack(() => { - debouncedSetMinMaxMarkers(); - }); + debouncedSetMinMaxMarkers(); }); }); @@ -522,7 +599,7 @@ function createSeriesGroup({ }); const inRange = createMemo(() => { - const range = activeRange(); + const range = activeIds(); if (range.length) { const start = chunkIdToIndex(scale, range.at(0)!); diff --git a/app/src/scripts/presets/coinblocks/index.ts b/app/src/scripts/presets/coinblocks/index.ts index d4c7ee45a..0b12d67e5 100644 --- a/app/src/scripts/presets/coinblocks/index.ts +++ b/app/src/scripts/presets/coinblocks/index.ts @@ -540,7 +540,7 @@ export function createPresets({ 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({ 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, diff --git a/app/src/scripts/presets/index.ts b/app/src/scripts/presets/index.ts index f6cca5b7e..3196e7df3 100644 --- a/app/src/scripts/presets/index.ts +++ b/app/src/scripts/presets/index.ts @@ -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 && diff --git a/app/src/scripts/presets/market/averages/index.ts b/app/src/scripts/presets/market/averages/index.ts index 647c60922..3f0c21a70 100644 --- a/app/src/scripts/presets/market/averages/index.ts +++ b/app/src/scripts/presets/market/averages/index.ts @@ -45,7 +45,7 @@ function createPresetFolder({ key, }: { scale: ResourceScale; - color: string; + color: Color; name: string; key: AverageName; }) { diff --git a/app/src/scripts/presets/templates/cohort.ts b/app/src/scripts/presets/templates/cohort.ts index 33b25ba21..727b575b4 100644 --- a/app/src/scripts/presets/templates/cohort.ts +++ b/app/src/scripts/presets/templates/cohort.ts @@ -12,7 +12,7 @@ export function createCohortPresetFolder({ scale: Scale; name: string; datasetKey: AnyPossibleCohortKey; - color: string; + color: Color; title: string; }) { return { @@ -38,7 +38,7 @@ export function createCohortPresetList({ scale: Scale; datasetKey: AnyPossibleCohortKey; title: string; - color: string; + color: Color; }) { const datasetPrefix = datasetKey ? (`${datasetKey}_` as const) diff --git a/app/src/scripts/presets/types.d.ts b/app/src/scripts/presets/types.d.ts index d38124ce7..f990c67f8 100644 --- a/app/src/scripts/presets/types.d.ts +++ b/app/src/scripts/presets/types.d.ts @@ -25,8 +25,8 @@ type ApplyPreset = (params: { datasets: Datasets; preset: Preset; legendSetter: Setter; - dark: boolean; - activeRange: RWS; + dark: Accessor; + activeIds: RWS; }) => void; interface PartialPresetFolder { diff --git a/app/src/scripts/utils/colors.ts b/app/src/scripts/utils/colors.ts index 3830d766c..03b811f07 100644 --- a/app/src/scripts/utils/colors.ts +++ b/app/src/scripts/utils/colors.ts @@ -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) { + return dark() ? redTailwind[300] : redTailwind[800]; +} +function red(dark: Accessor) { + return dark() ? redTailwind[500] : redTailwind[600]; +} +function darkRed(dark: Accessor) { + return dark() ? redTailwind[900] : redTailwind[100]; +} +function orange(dark: Accessor) { + return dark() ? orangeTailwind[500] : orangeTailwind[600]; +} +function darkOrange(dark: Accessor) { + return dark() ? orangeTailwind[900] : orangeTailwind[100]; +} +function amber(dark: Accessor) { + return dark() ? amberTailwind[500] : amberTailwind[600]; +} +function darkAmber(dark: Accessor) { + return dark() ? amberTailwind[900] : amberTailwind[100]; +} +function yellow(dark: Accessor) { + return dark() ? yellowTailwind[500] : yellowTailwind[600]; +} +function darkYellow(dark: Accessor) { + return dark() ? yellowTailwind[500] : yellowTailwind[600]; +} +function lime(dark: Accessor) { + return dark() ? limeTailwind[500] : limeTailwind[600]; +} +function darkLime(dark: Accessor) { + return dark() ? limeTailwind[900] : limeTailwind[100]; +} +function green(dark: Accessor) { + return dark() ? greenTailwind[500] : greenTailwind[600]; +} +function darkGreen(dark: Accessor) { + return dark() ? greenTailwind[900] : greenTailwind[100]; +} +function lightEmerald(dark: Accessor) { + return dark() ? emeraldTailwind[300] : emeraldTailwind[800]; +} +function emerald(dark: Accessor) { + return dark() ? emeraldTailwind[500] : emeraldTailwind[600]; +} +function darkEmerald(dark: Accessor) { + return dark() ? emeraldTailwind[900] : emeraldTailwind[100]; +} +function teal(dark: Accessor) { + return dark() ? tealTailwind[500] : tealTailwind[600]; +} +function darkTeal(dark: Accessor) { + return dark() ? tealTailwind[900] : tealTailwind[100]; +} +function cyan(dark: Accessor) { + return dark() ? cyanTailwind[500] : cyanTailwind[600]; +} +function darkCyan(dark: Accessor) { + return dark() ? cyanTailwind[900] : cyanTailwind[100]; +} +function sky(dark: Accessor) { + return dark() ? skyTailwind[500] : skyTailwind[600]; +} +function darkSky(dark: Accessor) { + return dark() ? skyTailwind[900] : skyTailwind[100]; +} +function blue(dark: Accessor) { + return dark() ? blueTailwind[500] : blueTailwind[600]; +} +function darkBlue(dark: Accessor) { + return dark() ? blueTailwind[900] : blueTailwind[100]; +} +function indigo(dark: Accessor) { + return dark() ? indigoTailwind[500] : indigoTailwind[600]; +} +function darkIndigo(dark: Accessor) { + return dark() ? indigoTailwind[900] : indigoTailwind[100]; +} +function violet(dark: Accessor) { + return dark() ? violetTailwind[500] : violetTailwind[600]; +} +function darkViolet(dark: Accessor) { + return dark() ? violetTailwind[900] : violetTailwind[100]; +} +function purple(dark: Accessor) { + return dark() ? purpleTailwind[500] : purpleTailwind[600]; +} +function darkPurple(dark: Accessor) { + return dark() ? purpleTailwind[900] : purpleTailwind[100]; +} +function fuchsia(dark: Accessor) { + return dark() ? fuchsiaTailwind[500] : fuchsiaTailwind[600]; +} +function darkFuchsia(dark: Accessor) { + return dark() ? fuchsiaTailwind[900] : fuchsiaTailwind[100]; +} +function pink(dark: Accessor) { + return dark() ? pinkTailwind[500] : pinkTailwind[600]; +} +function darkPink(dark: Accessor) { + return dark() ? pinkTailwind[900] : pinkTailwind[100]; +} +function rose(dark: Accessor) { + return dark() ? roseTailwind[500] : roseTailwind[600]; +} +function darkRose(dark: Accessor) { + return dark() ? roseTailwind[900] : roseTailwind[100]; +} -const darkWhite = grayTailwind[400]; -const gray = grayTailwind[600]; +function darkWhite(dark: Accessor) { + return dark() ? grayTailwind[400] : grayTailwind[400]; +} +function gray(dark: Accessor) { + return dark() ? grayTailwind[600] : grayTailwind[400]; +} -const black = "#000000"; -const white = "#ffffff"; +function white(dark: Accessor) { + return dark() ? "#ffffff" : "#000000"; +} + +function black(dark: Accessor) { + return dark() ? "#000000" : "#ffffff"; +} export const convertCandleToCandleColor = ( candle: { close: number; open: number }, diff --git a/app/src/scripts/utils/date.ts b/app/src/scripts/utils/date.ts index 38a48cb12..ce7fc32e3 100644 --- a/app/src/scripts/utils/date.ts +++ b/app/src/scripts/utils/date.ts @@ -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)); diff --git a/app/src/scripts/utils/sleep.ts b/app/src/scripts/utils/sleep.ts index db3b9b4bc..8b27999eb 100644 --- a/app/src/scripts/utils/sleep.ts +++ b/app/src/scripts/utils/sleep.ts @@ -3,7 +3,3 @@ export function sleep(ms: number) { setTimeout(resolve, ms); }); } - -export function tick() { - return sleep(1); -} diff --git a/app/src/scripts/utils/tick.ts b/app/src/scripts/utils/tick.ts index 1fadfb07f..dd0c975b8 100644 --- a/app/src/scripts/utils/tick.ts +++ b/app/src/scripts/utils/tick.ts @@ -1,5 +1,5 @@ +import { sleep } from "./sleep"; + export function tick() { - return new Promise((resolve) => { - setTimeout(resolve, 0); - }); + return sleep(0); } diff --git a/app/src/scripts/utils/types.d.ts b/app/src/scripts/utils/types.d.ts new file mode 100644 index 000000000..2b3ecbaba --- /dev/null +++ b/app/src/scripts/utils/types.d.ts @@ -0,0 +1 @@ +type Color = (dark: Accessor) => string; diff --git a/app/src/styles.css b/app/src/styles.css index f578e45ca..e52712405 100644 --- a/app/src/styles.css +++ b/app/src/styles.css @@ -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;