feat(frontend): add visual indicator to top lists

This commit is contained in:
Julian Tölle 2021-05-22 17:32:20 +02:00
parent 3228b22741
commit 8377b2f6d0
6 changed files with 79 additions and 13 deletions

View file

@ -5,6 +5,7 @@ import { TimeOptions } from "../api/entities/time-options";
import { TimePreset } from "../api/entities/time-preset.enum";
import { useTopAlbums } from "../hooks/use-api";
import { useAuth } from "../hooks/use-auth";
import { getMaxCount } from "../util/getMaxCount";
import { ReportTimeOptions } from "./ReportTimeOptions";
import { TopListItem } from "./TopListItem";
@ -27,6 +28,7 @@ export const ReportTopAlbums: React.FC = () => {
const { topAlbums, isLoading } = useTopAlbums(options);
const reportHasItems = !isLoading && topAlbums.length !== 0;
const maxCount = getMaxCount(topAlbums);
if (!user) {
return <Redirect to="/" />;
@ -55,7 +57,12 @@ export const ReportTopAlbums: React.FC = () => {
)}
{reportHasItems &&
topAlbums.map(({ album, count }) => (
<ReportItem album={album} count={count} />
<ReportItem
key={album.id}
album={album}
count={count}
maxCount={maxCount}
/>
))}
</div>
</div>
@ -66,7 +73,8 @@ export const ReportTopAlbums: React.FC = () => {
const ReportItem: React.FC<{
album: Album;
count: number;
}> = ({ album, count }) => {
maxCount: number;
}> = ({ album, count, maxCount }) => {
const artists = album.artists?.map((artist) => artist.name).join(", ") || "";
return (
@ -75,6 +83,7 @@ const ReportItem: React.FC<{
title={album.name}
subTitle={artists}
count={count}
maxCount={maxCount}
/>
);
};

View file

@ -4,6 +4,7 @@ import { TimeOptions } from "../api/entities/time-options";
import { TimePreset } from "../api/entities/time-preset.enum";
import { useTopArtists } from "../hooks/use-api";
import { useAuth } from "../hooks/use-auth";
import { getMaxCount } from "../util/getMaxCount";
import { ReportTimeOptions } from "./ReportTimeOptions";
import { TopListItem } from "./TopListItem";
@ -26,6 +27,7 @@ export const ReportTopArtists: React.FC = () => {
const { topArtists, isLoading } = useTopArtists(options);
const reportHasItems = !isLoading && topArtists.length !== 0;
const maxCount = getMaxCount(topArtists);
if (!user) {
return <Redirect to="/" />;
@ -54,7 +56,12 @@ export const ReportTopArtists: React.FC = () => {
)}
{reportHasItems &&
topArtists.map(({ artist, count }) => (
<TopListItem key={artist.id} title={artist.name} count={count} />
<TopListItem
key={artist.id}
title={artist.name}
count={count}
maxCount={maxCount}
/>
))}
</div>
</div>

View file

@ -5,6 +5,7 @@ import { TimePreset } from "../api/entities/time-preset.enum";
import { Track } from "../api/entities/track";
import { useTopTracks } from "../hooks/use-api";
import { useAuth } from "../hooks/use-auth";
import { getMaxCount } from "../util/getMaxCount";
import { ReportTimeOptions } from "./ReportTimeOptions";
import { TopListItem } from "./TopListItem";
@ -32,6 +33,8 @@ export const ReportTopTracks: React.FC = () => {
return <Redirect to="/" />;
}
const maxCount = getMaxCount(topTracks);
return (
<div className="md:flex md:justify-center p-4">
<div className="md:flex-shrink-0 min-w-full xl:min-w-0 xl:w-2/3 max-w-screen-lg">
@ -55,7 +58,7 @@ export const ReportTopTracks: React.FC = () => {
)}
{reportHasItems &&
topTracks.map(({ track, count }) => (
<ReportItem track={track} count={count} />
<ReportItem track={track} count={count} maxCount={maxCount} />
))}
</div>
</div>
@ -66,7 +69,8 @@ export const ReportTopTracks: React.FC = () => {
const ReportItem: React.FC<{
track: Track;
count: number;
}> = ({ track, count }) => {
maxCount: number;
}> = ({ track, count, maxCount }) => {
const artists = track.artists?.map((artist) => artist.name).join(", ") || "";
return (
@ -75,6 +79,7 @@ const ReportItem: React.FC<{
title={track.name}
subTitle={artists}
count={count}
maxCount={maxCount}
/>
);
};

View file

@ -1,23 +1,55 @@
import React from "react";
export const TopListItem: React.FC<{
export interface TopListItemProps {
key: string;
title: string;
subTitle?: string;
count: number;
}> = ({ key, title, subTitle, count }) => {
/**
* Highest Number that is displayed in the top list. Used to display a "progress bar".
*/
maxCount?: number;
}
export const TopListItem: React.FC<TopListItemProps> = ({
key,
title,
subTitle,
count,
maxCount,
}) => {
return (
<div
key={key}
className="hover:bg-gray-100 border-b border-gray-200 flex md:justify-around text-gray-700 md:px-2 py-2"
className="group hover:bg-gray-200 bg-gray-100 border-b border-gray-200 md:justify-around text-gray-700 md:px-2"
>
<div className="md:flex w-11/12">
<div className={`${subTitle ? "md:w-1/2" : "md:w-full"} font-bold`}>
{title}
<div className="flex pt-2">
<div className="md:flex w-11/12">
<div className={`${subTitle ? "md:w-1/2" : "md:w-full"} font-bold`}>
{title}
</div>
{subTitle && <div className="md:w-1/3">{subTitle}</div>}
</div>
{subTitle && <div className="md:w-1/3">{subTitle}</div>}
<div className="w-1/12 self-center">{count}</div>
</div>
<div className="w-1/12 self-center">{count}</div>
{maxCount && isMaxCountValid(maxCount) && (
<div className="h-1 w-full bg-gradient-to-r from-teal-200 via-green-400 to-violet-400 flex flex-row-reverse">
<div
style={{ width: numberToPercent(1 - count / maxCount) }}
className="h-full group-hover:bg-gray-200 bg-gray-100"
></div>
</div>
)}
</div>
);
};
const isMaxCountValid = (maxCount: number) =>
!(Number.isNaN(maxCount) || maxCount === 0);
const numberToPercent = (ratio: number) =>
ratio.toLocaleString(undefined, {
style: "percent",
minimumFractionDigits: 2,
});

View file

@ -0,0 +1,11 @@
interface TopListItemEntity {
count: number;
}
/**
* Get max count for top list. Returns at least 1 to make sure we do not run into issues
* with empty list (would normally return -Infinity) or 0 (could cause divide by zero error).
*/
export function getMaxCount(items: TopListItemEntity[]): number {
return Math.max(1, ...items.map(({ count }) => count));
}

View file

@ -39,6 +39,8 @@ module.exports = {
},
yellow: colors.yellow,
teal: colors.teal,
violet: colors.violet,
},
},
};