From 9389fbbd941b365f95dd6195cf04c725ef9a908b Mon Sep 17 00:00:00 2001 From: ToastedToast Date: Fri, 5 Jun 2026 13:51:50 +0800 Subject: [PATCH] add daily, weekly, and monthly gain modes --- pnpm-workspace.yaml | 4 ++ src/lib/components/chart.svelte | 16 +++++--- src/routes/+page.svelte | 71 ++++++++++++++++++++++++++++++++- 3 files changed, 83 insertions(+), 8 deletions(-) diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 8de8021..4c98d3d 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,3 +1,7 @@ +allowBuilds: + esbuild: true + sharp: true + workerd: true onlyBuiltDependencies: - "@tailwindcss/oxide" - esbuild diff --git a/src/lib/components/chart.svelte b/src/lib/components/chart.svelte index 83da75c..5bddfa0 100644 --- a/src/lib/components/chart.svelte +++ b/src/lib/components/chart.svelte @@ -13,9 +13,16 @@ interface Props { title: string; series: Partial[]; + xAxisDateFormat?: string; + tooltipDateFormat?: string; } - const { title, series }: Props = $props(); + let { + title, + series, + xAxisDateFormat = "%b %d, %Y", + tooltipDateFormat = "%b %d, %Y %H:%M:%S", + }: Props = $props(); let options = $derived({ chart: { @@ -72,7 +79,7 @@ xAxis: { type: "datetime", labels: { - format: "{value:%b %d, %Y}", + format: `{value:${xAxisDateFormat}}`, style: { color: "var(--foreground)", fontSize: "12px", @@ -112,13 +119,10 @@ style: { color: "#fff", }, - xDateFormat: "%B %d, %Y", shared: true, shadow: false, formatter: function (this: any): any { - return `${moment( - this.points[0].x - ).format("YYYY-MM-DD HH:mm:ss")}
${this.points + return `${Highcharts.dateFormat(tooltipDateFormat, this.points[0].x)}
${this.points .map( (point: any) => `${point.series.name}: ${Highcharts.numberFormat( diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index b6e0147..338ab71 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -27,6 +27,7 @@ type: z.enum(["channel", "video"]).default("channel"), source: z.enum(sources.map((source) => source.value)).default("vidiq"), id: z.string().default(""), + mode: z.enum(["total", "daily", "weekly", "monthly"]).default("total"), }); const params = useSearchParams(schema); @@ -50,6 +51,14 @@ type Snapshot = ChannelSnapshot | VideoSnapshot; type StatKey = Exclude; type ExportPeriod = "all" | "hourly" | "daily" | "weekly" | "monthly"; + type GainsMode = "total" | "daily" | "weekly" | "monthly"; + + const modes: { label: string; value: GainsMode }[] = [ + { label: "Total", value: "total" }, + { label: "Daily", value: "daily" }, + { label: "Weekly", value: "weekly" }, + { label: "Monthly", value: "monthly" }, + ]; let data = $state.raw([]); let flipRowsAndColumns = $state.raw(false); @@ -237,6 +246,7 @@ ); let type = $derived(params.type); + let mode = $derived(params.mode); $effect(() => { type; @@ -262,6 +272,41 @@ return null; }; + const computeGains = (snapshots: Snapshot[], mode: GainsMode) => { + if (mode === "total") return snapshots; + + const aggregated = aggregateSnapshots(mode as ExportPeriod); + const statKeys = new Set(stats.map((s) => s.key)); + + return aggregated.map((cur, i) => { + if (i === 0) { + const zeroed = { date: cur.date } as Snapshot; + for (const key of statKeys) { + (zeroed as any)[key] = 0; + } + return zeroed; + } + + const prev = aggregated[i - 1]; + const gained = { date: cur.date } as Snapshot; + + for (const key of statKeys) { + const curVal = getStatValue(cur, key); + const prevVal = getStatValue(prev, key); + + if (curVal === null || prevVal === null) { + (gained as any)[key] = null; + } else { + (gained as any)[key] = curVal - prevVal; + } + } + + return gained; + }); + }; + + let chartData = $derived(computeGains(data, mode)); + const escapeCsvValue = (value: string | number | null) => { if (value === null) { return ""; @@ -641,13 +686,35 @@

{error}

{/if} {#if status !== "loading" && data.length > 0 && stats} +
+ {#each modes as m (m.value)} + + {/each} +
{#each stats as stat (stat.key)} [ + data: chartData.map((d) => [ new Date(d.date).getTime(), getStatValue(d, stat.key), ]),