diff --git a/frontend/css/entity-pages.less b/frontend/css/entity-pages.less index 3e88564ea5..215badc0cb 100644 --- a/frontend/css/entity-pages.less +++ b/frontend/css/entity-pages.less @@ -169,6 +169,9 @@ justify-items: center; grid-template-columns: repeat(auto-fill, minmax(190px, max-content)); grid-template-rows: repeat(2, 1fr); + &.single-row { + grid-template-rows: repeat(1, 1fr); + } .cover-art { aspect-ratio: 1; background: lightgrey; diff --git a/frontend/js/src/artist/ArtistPage.tsx b/frontend/js/src/artist/ArtistPage.tsx index ccbff5ccf7..b2f68f8701 100644 --- a/frontend/js/src/artist/ArtistPage.tsx +++ b/frontend/js/src/artist/ArtistPage.tsx @@ -7,7 +7,7 @@ import { faPlayCircle, faUserAstronaut, } from "@fortawesome/free-solid-svg-icons"; -import { chain, isEmpty, isUndefined, partition, sortBy } from "lodash"; +import { chain, isEmpty, isUndefined, orderBy, groupBy, sortBy } from "lodash"; import { sanitize } from "dompurify"; import { Link, @@ -19,7 +19,7 @@ import { import { Helmet } from "react-helmet"; import { useQuery } from "@tanstack/react-query"; import NiceModal from "@ebay/nice-modal-react"; -import GlobalAppContext from "../utils/GlobalAppContext"; +import { faCalendar } from "@fortawesome/free-regular-svg-icons"; import { getReviewEventContent } from "../utils/utils"; import TagsComponent from "../tags/TagsComponent"; import ListenCard from "../common/listens/ListenCard"; @@ -39,11 +39,43 @@ import { RouteQuery } from "../utils/Loader"; import { useBrainzPlayerDispatch } from "../common/brainzplayer/BrainzPlayerContext"; import SimilarArtistComponent from "../explore/music-neighborhood/components/SimilarArtist"; import CBReviewModal from "../cb-review/CBReviewModal"; +import Pill from "../components/Pill"; + +function SortingButtons({ + sort, + setSort, +}: { + sort: "release_date" | "total_listen_count"; + setSort: (sort: "release_date" | "total_listen_count") => void; +}): JSX.Element { + return ( +
+ setSort("release_date")} + > + + + setSort("total_listen_count")} + > + + +
+ ); +} + +interface ReleaseGroupWithSecondaryTypes extends ReleaseGroup { + secondary_types: string[]; +} export type ArtistPageProps = { popularRecordings: PopularRecording[]; artist: MusicBrainzArtist; - releaseGroups: ReleaseGroup[]; + releaseGroups: ReleaseGroupWithSecondaryTypes[]; similarArtists: { artists: SimilarArtist[]; topReleaseGroupColor: ReleaseColor | undefined; @@ -53,9 +85,10 @@ export type ArtistPageProps = { coverArt?: string; }; +const COVER_ART_SINGLE_ROW_COUNT = 8; + export default function ArtistPage(): JSX.Element { const _ = useLoaderData(); - const { APIService } = React.useContext(GlobalAppContext); const location = useLocation(); const params = useParams() as { artistMBID: string }; const { artistMBID } = params; @@ -84,11 +117,44 @@ export default function ArtistPage(): JSX.Element { WikipediaExtract >(); - const [albumsByThisArtist, alsoAppearsOn] = partition( + const [sort, setSort] = React.useState<"release_date" | "total_listen_count">( + "release_date" + ); + + const rgGroups = groupBy( releaseGroups, - (rg) => rg.artists[0].artist_mbid === artist?.artist_mbid + (rg) => + (rg.type ?? "Other") + + (rg.secondary_types?.[0] ? ` + ${rg.secondary_types?.[0]}` : "") ); + const sortReleaseGroups = ( + releaseGroupsInput: ReleaseGroupWithSecondaryTypes[] + ) => + orderBy( + releaseGroupsInput, + [ + sort === "release_date" ? (rg) => rg.date || "" : "total_listen_count", + sort === "release_date" ? "total_listen_count" : (rg) => rg.date || "", + "name", + ], + ["desc", "desc", "asc"] + ); + + const typeOrder = ["Album", "Single", "EP", "Broadcast", "Other"]; + const sortedRgGroupsKeys = sortBy(Object.keys(rgGroups), [ + (type) => typeOrder.indexOf(type.split(" + ")[0]), + (type) => type.split(" + ")[1] ?? "", + ]); + + const groupedReleaseGroups: Record< + string, + ReleaseGroupWithSecondaryTypes[] + > = {}; + sortedRgGroupsKeys.forEach((type) => { + groupedReleaseGroups[type] = sortReleaseGroups(rgGroups[type]); + }); + React.useEffect(() => { async function fetchReviews() { try { @@ -407,20 +473,21 @@ export default function ArtistPage(): JSX.Element { )} -
-

Albums

-
- {albumsByThisArtist.map(getReleaseCard)} -
-
- {Boolean(alsoAppearsOn?.length) && ( + {Object.entries(groupedReleaseGroups).map(([type, rgGroup]) => (
-

Also appears on

-
- {alsoAppearsOn.map(getReleaseCard)} +
+

{type}

+ +
+
+ {rgGroup.map(getReleaseCard)}
- )} + ))}
{similarArtists && similarArtists.artists.length > 0 ? ( diff --git a/listenbrainz/webserver/views/entity_pages.py b/listenbrainz/webserver/views/entity_pages.py index a913105670..9d6edd813c 100644 --- a/listenbrainz/webserver/views/entity_pages.py +++ b/listenbrainz/webserver/views/entity_pages.py @@ -20,17 +20,6 @@ release_group_bp = Blueprint("release-group", __name__) -def get_release_group_sort_key(release_group): - """ Return a tuple that sorts release group by total_listen_count and then by date """ - release_date = release_group.get("date") - if release_date is None: - release_date = datetime.min - else: - release_date = datetime.strptime(release_date, "%Y-%m-%d") - - return release_group["total_listen_count"] or 0, release_date - - def get_cover_art_for_artist(release_groups): """ Get the cover art for an artist using a list of their release groups """ covers = [] @@ -170,8 +159,6 @@ def artist_entity(artist_mbid): release_group["total_user_count"] = pop["total_user_count"] release_groups.append(release_group) - release_groups.sort(key=get_release_group_sort_key, reverse=True) - listening_stats = get_entity_listener(db_conn, "artists", artist_mbid, "all_time") if listening_stats is None: listening_stats = {