Fix race condition with using getComputedStyle primary color for dynamic activity grid darkening

Instead just use the color from the current theme directly. Tested works on initial load and theme changes.
Fixes https://github.com/gabehf/Koito/issues/75
dev
Michael Landry 2 months ago committed by Gabe Farrell
parent 56ffe0a041
commit 517cc8ac28

@ -1,41 +1,41 @@
import { useQuery } from "@tanstack/react-query" import { useQuery } from "@tanstack/react-query";
import { getActivity, type getActivityArgs, type ListenActivityItem } from "api/api" import {
import Popup from "./Popup" getActivity,
import { useState } from "react" type getActivityArgs,
import { useTheme } from "~/hooks/useTheme" type ListenActivityItem,
import ActivityOptsSelector from "./ActivityOptsSelector" } from "api/api";
import type { Theme } from "~/styles/themes.css" import Popup from "./Popup";
import { useState } from "react";
import { useTheme } from "~/hooks/useTheme";
import ActivityOptsSelector from "./ActivityOptsSelector";
import type { Theme } from "~/styles/themes.css";
function getPrimaryColor(theme: Theme): string { function getPrimaryColor(theme: Theme): string {
const value = theme.primary; const value = theme.primary;
const rgbMatch = value.match(/^rgb\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)$/); const rgbMatch = value.match(
/^rgb\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)$/
);
if (rgbMatch) { if (rgbMatch) {
const [, r, g, b] = rgbMatch.map(Number); const [, r, g, b] = rgbMatch.map(Number);
return ( return "#" + [r, g, b].map((n) => n.toString(16).padStart(2, "0")).join("");
'#' +
[r, g, b]
.map((n) => n.toString(16).padStart(2, '0'))
.join('')
);
} }
return value; return value;
} }
interface Props { interface Props {
step?: string step?: string;
range?: number range?: number;
month?: number month?: number;
year?: number year?: number;
artistId?: number artistId?: number;
albumId?: number albumId?: number;
trackId?: number trackId?: number;
configurable?: boolean configurable?: boolean;
autoAdjust?: boolean autoAdjust?: boolean;
} }
export default function ActivityGrid({ export default function ActivityGrid({
step = 'day', step = "day",
range = 182, range = 182,
month = 0, month = 0,
year = 0, year = 0,
@ -44,13 +44,12 @@ export default function ActivityGrid({
trackId = 0, trackId = 0,
configurable = false, configurable = false,
}: Props) { }: Props) {
const [stepState, setStep] = useState(step);
const [stepState, setStep] = useState(step) const [rangeState, setRange] = useState(range);
const [rangeState, setRange] = useState(range)
const { isPending, isError, data, error } = useQuery({ const { isPending, isError, data, error } = useQuery({
queryKey: [ queryKey: [
'listen-activity', "listen-activity",
{ {
step: stepState, step: stepState,
range: rangeState, range: rangeState,
@ -58,41 +57,41 @@ export default function ActivityGrid({
year: year, year: year,
artist_id: artistId, artist_id: artistId,
album_id: albumId, album_id: albumId,
track_id: trackId track_id: trackId,
}, },
], ],
queryFn: ({ queryKey }) => getActivity(queryKey[1] as getActivityArgs), queryFn: ({ queryKey }) => getActivity(queryKey[1] as getActivityArgs),
}); });
const { theme, themeName } = useTheme(); const { theme, themeName } = useTheme();
const color = getPrimaryColor(theme); const color = getPrimaryColor(theme);
if (isPending) { if (isPending) {
return ( return (
<div className="w-[500px]"> <div className="w-[500px]">
<h2>Activity</h2> <h2>Activity</h2>
<p>Loading...</p> <p>Loading...</p>
</div> </div>
) );
} }
if (isError) return <p className="error">Error:{error.message}</p> if (isError) return <p className="error">Error:{error.message}</p>;
// from https://css-tricks.com/snippets/javascript/lighten-darken-color/ // from https://css-tricks.com/snippets/javascript/lighten-darken-color/
function LightenDarkenColor(hex: string, lum: number) { function LightenDarkenColor(hex: string, lum: number) {
// validate hex string // validate hex string
hex = String(hex).replace(/[^0-9a-f]/gi, ''); hex = String(hex).replace(/[^0-9a-f]/gi, "");
if (hex.length < 6) { if (hex.length < 6) {
hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2]; hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
} }
lum = lum || 0; lum = lum || 0;
// convert to decimal and change luminosity // convert to decimal and change luminosity
var rgb = "#", c, i; var rgb = "#",
c,
i;
for (i = 0; i < 3; i++) { for (i = 0; i < 3; i++) {
c = parseInt(hex.substring(i*2,(i*2)+2), 16); c = parseInt(hex.substring(i * 2, i * 2 + 2), 16);
c = Math.round(Math.min(Math.max(0, c + (c * lum)), 255)).toString(16); c = Math.round(Math.min(Math.max(0, c + c * lum), 255)).toString(16);
rgb += ("00" + c).substring(c.length); rgb += ("00" + c).substring(c.length);
} }
@ -100,39 +99,39 @@ export default function ActivityGrid({
} }
const getDarkenAmount = (v: number, t: number): number => { const getDarkenAmount = (v: number, t: number): number => {
// really ugly way to just check if this is for all items and not a specific item. // 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. // is it jsut better to just pass the target in as a var? probably.
const adjustment = artistId == albumId && albumId == trackId && trackId == 0 ? 10 : 1 const adjustment =
artistId == albumId && albumId == trackId && trackId == 0 ? 10 : 1;
// automatically adjust the target value based on step // automatically adjust the target value based on step
// the smartest way to do this would be to have the api return the // the smartest way to do this would be to have the api return the
// highest value in the range. too bad im not smart // highest value in the range. too bad im not smart
switch (stepState) { switch (stepState) {
case 'day': case "day":
t = 10 * adjustment t = 10 * adjustment;
break; break;
case 'week': case "week":
t = 20 * adjustment t = 20 * adjustment;
break; break;
case 'month': case "month":
t = 50 * adjustment t = 50 * adjustment;
break; break;
case 'year': case "year":
t = 100 * adjustment t = 100 * adjustment;
break; break;
} }
v = Math.min(v, t) v = Math.min(v, t);
if (themeName === "pearl") { if (themeName === "pearl") {
// special case for the only light theme lol // special case for the only light theme lol
// could be generalized by pragmatically comparing the // could be generalized by pragmatically comparing the
// lightness of the bg vs the primary but eh // lightness of the bg vs the primary but eh
return ((t-v) / t) return (t - v) / t;
} else { } else {
return ((v-t) / t) * .8 return ((v - t) / t) * 0.8;
}
} }
};
const CHUNK_SIZE = 26 * 7; const CHUNK_SIZE = 26 * 7;
const chunks = []; const chunks = [];
@ -167,18 +166,25 @@ export default function ActivityGrid({
position="top" position="top"
space={12} space={12}
extraClasses="left-2" extraClasses="left-2"
inner={`${new Date(item.start_time).toLocaleDateString()} ${item.listens} plays`} inner={`${new Date(item.start_time).toLocaleDateString()} ${
item.listens
} plays`}
> >
<div <div
style={{ style={{
display: 'inline-block', display: "inline-block",
background: background:
item.listens > 0 item.listens > 0
? LightenDarkenColor(color, getDarkenAmount(item.listens, 100)) ? LightenDarkenColor(
: 'var(--color-bg-secondary)', 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] ${ 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)' item.listens > 0
? ""
: "border-[0.5px] border-(--color-bg-tertiary)"
}`} }`}
></div> ></div>
</Popup> </Popup>

Loading…
Cancel
Save