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,191 +1,197 @@
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(
if (rgbMatch) { /^rgb\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)$/
const [, r, g, b] = rgbMatch.map(Number); );
return ( if (rgbMatch) {
'#' + const [, r, g, b] = rgbMatch.map(Number);
[r, g, b] return "#" + [r, g, b].map((n) => n.toString(16).padStart(2, "0")).join("");
.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,
artistId = 0, artistId = 0,
albumId = 0, albumId = 0,
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, month: month,
month: month, 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 color = getPrimaryColor(theme);
const { theme, themeName } = useTheme();
const color = getPrimaryColor(theme); if (isPending) {
return (
<div className="w-[500px]">
if (isPending) { <h2>Activity</h2>
return ( <p>Loading...</p>
<div className="w-[500px]"> </div>
<h2>Activity</h2> );
<p>Loading...</p> }
</div> if (isError) return <p className="error">Error:{error.message}</p>;
)
// from https://css-tricks.com/snippets/javascript/lighten-darken-color/
function LightenDarkenColor(hex: string, lum: number) {
// validate hex string
hex = String(hex).replace(/[^0-9a-f]/gi, "");
if (hex.length < 6) {
hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
} }
if (isError) return <p className="error">Error:{error.message}</p> lum = lum || 0;
// from https://css-tricks.com/snippets/javascript/lighten-darken-color/ // convert to decimal and change luminosity
function LightenDarkenColor(hex: string, lum: number) { var rgb = "#",
// validate hex string c,
hex = String(hex).replace(/[^0-9a-f]/gi, ''); i;
if (hex.length < 6) { for (i = 0; i < 3; i++) {
hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2]; c = parseInt(hex.substring(i * 2, i * 2 + 2), 16);
} c = Math.round(Math.min(Math.max(0, c + c * lum), 255)).toString(16);
lum = lum || 0; rgb += ("00" + c).substring(c.length);
// convert to decimal and change luminosity
var rgb = "#", c, i;
for (i = 0; i < 3; i++) {
c = parseInt(hex.substring(i*2,(i*2)+2), 16);
c = Math.round(Math.min(Math.max(0, c + (c * lum)), 255)).toString(16);
rgb += ("00"+c).substring(c.length);
}
return rgb;
} }
const getDarkenAmount = (v: number, t: number): number => { return rgb;
}
// 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 getDarkenAmount = (v: number, t: number): number => {
const adjustment = artistId == albumId && albumId == trackId && trackId == 0 ? 10 : 1 // 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.
// automatically adjust the target value based on step const adjustment =
// the smartest way to do this would be to have the api return the artistId == albumId && albumId == trackId && trackId == 0 ? 10 : 1;
// highest value in the range. too bad im not smart
switch (stepState) { // automatically adjust the target value based on step
case 'day': // the smartest way to do this would be to have the api return the
t = 10 * adjustment // highest value in the range. too bad im not smart
break; switch (stepState) {
case 'week': case "day":
t = 20 * adjustment t = 10 * adjustment;
break; break;
case 'month': case "week":
t = 50 * adjustment t = 20 * adjustment;
break; break;
case 'year': case "month":
t = 100 * adjustment t = 50 * adjustment;
break; break;
} case "year":
t = 100 * adjustment;
v = Math.min(v, t) break;
if (themeName === "pearl") {
// special case for the only light theme lol
// could be generalized by pragmatically comparing the
// lightness of the bg vs the primary but eh
return ((t-v) / t)
} else {
return ((v-t) / t) * .8
}
} }
const CHUNK_SIZE = 26 * 7; v = Math.min(v, t);
const chunks = []; if (themeName === "pearl") {
// special case for the only light theme lol
for (let i = 0; i < data.length; i += CHUNK_SIZE) { // could be generalized by pragmatically comparing the
chunks.push(data.slice(i, i + CHUNK_SIZE)); // lightness of the bg vs the primary but eh
return (t - v) / t;
} else {
return ((v - t) / t) * 0.8;
} }
};
return (
<div className="flex flex-col items-start"> const CHUNK_SIZE = 26 * 7;
<h2>Activity</h2> const chunks = [];
{configurable ? (
<ActivityOptsSelector for (let i = 0; i < data.length; i += CHUNK_SIZE) {
rangeSetter={setRange} chunks.push(data.slice(i, i + CHUNK_SIZE));
currentRange={rangeState} }
stepSetter={setStep}
currentStep={stepState} return (
/> <div className="flex flex-col items-start">
) : null} <h2>Activity</h2>
{configurable ? (
{chunks.map((chunk, index) => ( <ActivityOptsSelector
rangeSetter={setRange}
currentRange={rangeState}
stepSetter={setStep}
currentStep={stepState}
/>
) : null}
{chunks.map((chunk, index) => (
<div
key={index}
className="w-auto grid grid-flow-col grid-rows-7 gap-[3px] md:gap-[5px] mb-4"
>
{chunk.map((item) => (
<div
key={new Date(item.start_time).toString()}
className="w-[10px] sm:w-[12px] h-[10px] sm:h-[12px]"
>
<Popup
position="top"
space={12}
extraClasses="left-2"
inner={`${new Date(item.start_time).toLocaleDateString()} ${
item.listens
} plays`}
>
<div <div
key={index} style={{
className="w-auto grid grid-flow-col grid-rows-7 gap-[3px] md:gap-[5px] mb-4" display: "inline-block",
> background:
{chunk.map((item) => ( item.listens > 0
<div ? LightenDarkenColor(
key={new Date(item.start_time).toString()} color,
className="w-[10px] sm:w-[12px] h-[10px] sm:h-[12px]" getDarkenAmount(item.listens, 100)
> )
<Popup : "var(--color-bg-secondary)",
position="top" }}
space={12} className={`w-[10px] sm:w-[12px] h-[10px] sm:h-[12px] rounded-[2px] md:rounded-[3px] ${
extraClasses="left-2" item.listens > 0
inner={`${new Date(item.start_time).toLocaleDateString()} ${item.listens} plays`} ? ""
> : "border-[0.5px] border-(--color-bg-tertiary)"
<div }`}
style={{ ></div>
display: 'inline-block', </Popup>
background: </div>
item.listens > 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)'
}`}
></div>
</Popup>
</div>
))}
</div>
))}
</div> </div>
); ))}
} </div>
);
}

Loading…
Cancel
Save