mirror of https://github.com/gabehf/Koito.git
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/75dev
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…
Reference in new issue