From 5a2cb437a16e2d8d2d29907583dc0cebf9821ebc Mon Sep 17 00:00:00 2001 From: Gabe Farrell Date: Tue, 17 Jun 2025 20:29:18 -0400 Subject: [PATCH] feat: make all activity grids configurable --- CHANGELOG.md | 9 +- client/app/components/ActivityGrid.tsx | 127 ++++++++++-------- .../app/components/ActivityOptsSelector.tsx | 103 +++++++------- client/app/routes/Home.tsx | 2 +- client/app/routes/MediaItems/Album.tsx | 2 +- client/app/routes/MediaItems/Artist.tsx | 2 +- client/app/routes/MediaItems/Track.tsx | 2 +- 7 files changed, 140 insertions(+), 107 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b34d38f..b628e57 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,5 +3,12 @@ ## Features - Allow loading environment variables from files using the _FILE suffix (#20) +## Enhancements + ## Fixes -- Sub-second precision is stripped from incoming listens to ensure they can be deleted reliably \ No newline at end of file +- Sub-second precision is stripped from incoming listens to ensure they can be deleted reliably + +## Updates +- Adjusted colors for the "Yuu" theme +- Themes now have a single source of truth in themes.css.ts +- Basline support for custom themes added \ No newline at end of file diff --git a/client/app/components/ActivityGrid.tsx b/client/app/components/ActivityGrid.tsx index 818d6e3..c1778d3 100644 --- a/client/app/components/ActivityGrid.tsx +++ b/client/app/components/ActivityGrid.tsx @@ -45,12 +45,16 @@ export default function ActivityGrid({ albumId = 0, trackId = 0, configurable = false, - autoAdjust = false, }: Props) { const [color, setColor] = useState(getPrimaryColor()) const [stepState, setStep] = useState(step) const [rangeState, setRange] = useState(range) + + // sometimes, a little bit of a lie for the sake of better design is necessary + if (rangeState === 365) { + setRange(rangeState - 1) + } const { isPending, isError, data, error } = useQuery({ queryKey: [ @@ -111,24 +115,26 @@ export default function ActivityGrid({ const getDarkenAmount = (v: number, t: number): number => { - if (autoAdjust) { - // automatically adjust the target value based on step - // the smartest way to do this would be to have the api return the - // highest value in the range. too bad im not smart - switch (stepState) { - case 'day': - t = 10 - break; - case 'week': - t = 20 - break; - case 'month': - t = 50 - break; - case 'year': - t = 100 - break; - } + // really ugly way to just check if this is for all items and not a specific item. + // is it jsut better to just pass the target in as a var? probably. + const adjustment = artistId == albumId && albumId == trackId && trackId == 0 ? 10 : 1 + + // automatically adjust the target value based on step + // the smartest way to do this would be to have the api return the + // highest value in the range. too bad im not smart + switch (stepState) { + case 'day': + t = 10 * adjustment + break; + case 'week': + t = 20 * adjustment + break; + case 'month': + t = 50 * adjustment + break; + case 'year': + t = 100 * adjustment + break; } v = Math.min(v, t) @@ -142,45 +148,58 @@ export default function ActivityGrid({ } } - return (
-

Activity

- {configurable ? ( - - ) : ( - '' - )} -
- {data.map((item) => ( + const CHUNK_SIZE = 26 * 7; + const chunks = []; + + for (let i = 0; i < data.length; i += CHUNK_SIZE) { + chunks.push(data.slice(i, i + CHUNK_SIZE)); + } + + return ( +
+

Activity

+ {configurable ? ( + + ) : null} + + {chunks.map((chunk, index) => (
- + {chunk.map((item) => (
0 - ? LightenDarkenColor(color, getDarkenAmount(item.listens, 100)) - : 'var(--color-bg-secondary)', - }} - className={`w-[10px] sm:w-[12px] h-[10px] sm:h-[12px] rounded-[2px] md:rounded-[3px] ${item.listens > 0 ? '' : 'border-[0.5px] border-(--color-bg-tertiary)'}`} - >
-
+ key={new Date(item.start_time).toString()} + className="w-[10px] sm:w-[12px] h-[10px] sm:h-[12px]" + > + +
0 + ? LightenDarkenColor(color, getDarkenAmount(item.listens, 100)) + : 'var(--color-bg-secondary)', + }} + className={`w-[10px] sm:w-[12px] h-[10px] sm:h-[12px] rounded-[2px] md:rounded-[3px] ${ + item.listens > 0 ? '' : 'border-[0.5px] border-(--color-bg-tertiary)' + }`} + >
+
+
+ ))}
))}
-
- ); -} +} diff --git a/client/app/components/ActivityOptsSelector.tsx b/client/app/components/ActivityOptsSelector.tsx index 213f8a6..dfca078 100644 --- a/client/app/components/ActivityOptsSelector.tsx +++ b/client/app/components/ActivityOptsSelector.tsx @@ -1,4 +1,5 @@ -import { useEffect } from "react"; +import { ChevronDown, ChevronUp } from "lucide-react"; +import { useEffect, useState } from "react"; interface Props { stepSetter: (value: string) => void; @@ -18,16 +19,15 @@ export default function ActivityOptsSelector({ const stepPeriods = ['day', 'week', 'month', 'year']; const rangePeriods = [105, 182, 365]; - const stepDisplay = (str: string): string => { - return str.split('_').map(w => + const [collapsed, setCollapsed] = useState(true); + + const stepDisplay = (str: string): string => + str.split('_').map(w => w.split('').map((char, index) => index === 0 ? char.toUpperCase() : char).join('') ).join(' '); - }; - const rangeDisplay = (r: number): string => { - return `${r}` - } + const rangeDisplay = (r: number): string => `${r}`; const setStep = (val: string) => { stepSetter(val); @@ -42,57 +42,64 @@ export default function ActivityOptsSelector({ localStorage.setItem('activity_range_' + window.location.pathname.split('/')[1], String(val)); } }; - + useEffect(() => { if (!disableCache) { const cachedRange = parseInt(localStorage.getItem('activity_range_' + window.location.pathname.split('/')[1]) ?? '35'); - if (cachedRange) { - rangeSetter(cachedRange); - } + if (cachedRange) rangeSetter(cachedRange); const cachedStep = localStorage.getItem('activity_step_' + window.location.pathname.split('/')[1]); - if (cachedStep) { - stepSetter(cachedStep); - } + if (cachedStep) stepSetter(cachedStep); } - }, []); + }, []); return ( -
-
-

Step:

- {stepPeriods.map((p, i) => ( -
- - - {i !== stepPeriods.length - 1 ? '|' : ''} - +
+ + + {!collapsed && ( + <> +
+

Step:

+ {stepPeriods.map((p, i) => ( +
+ + + {i !== stepPeriods.length - 1 ? '|' : ''} + +
+ ))}
- ))} -
-
-

Range:

- {rangePeriods.map((r, i) => ( -
- - - {i !== rangePeriods.length - 1 ? '|' : ''} - +
+

Range:

+ {rangePeriods.map((r, i) => ( +
+ + + {i !== rangePeriods.length - 1 ? '|' : ''} + +
+ ))}
- ))} -
+ + )}
); } diff --git a/client/app/routes/Home.tsx b/client/app/routes/Home.tsx index 52dc9be..11ee11d 100644 --- a/client/app/routes/Home.tsx +++ b/client/app/routes/Home.tsx @@ -26,7 +26,7 @@ export default function Home() {
- +
diff --git a/client/app/routes/MediaItems/Album.tsx b/client/app/routes/MediaItems/Album.tsx index 9751a87..955baec 100644 --- a/client/app/routes/MediaItems/Album.tsx +++ b/client/app/routes/MediaItems/Album.tsx @@ -52,7 +52,7 @@ export default function Album() {
- +
); diff --git a/client/app/routes/MediaItems/Artist.tsx b/client/app/routes/MediaItems/Artist.tsx index 272d5fb..41a185a 100644 --- a/client/app/routes/MediaItems/Artist.tsx +++ b/client/app/routes/MediaItems/Artist.tsx @@ -59,7 +59,7 @@ export default function Artist() {
- +
diff --git a/client/app/routes/MediaItems/Track.tsx b/client/app/routes/MediaItems/Track.tsx index 039d951..3f8cf07 100644 --- a/client/app/routes/MediaItems/Track.tsx +++ b/client/app/routes/MediaItems/Track.tsx @@ -54,7 +54,7 @@ export default function Track() {
- +
)