Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LB-1626: Sort Release Groups on Artist Page #2985

Merged
merged 11 commits into from
Oct 10, 2024
3 changes: 3 additions & 0 deletions frontend/css/entity-pages.less
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
4 changes: 2 additions & 2 deletions frontend/js/src/about/add-data/AddData.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -269,8 +269,8 @@ export default function AddData() {
<em>
<a href="https://scrobblerad.io/">ScrobbleRadio</a>
</em>
, a streaming radio player and listen submitter for a curated list
of global radio stations
, a streaming radio player and listen submitter for a curated list of
global radio stations
</li>
<li>
<em>
Expand Down
169 changes: 154 additions & 15 deletions frontend/js/src/artist/ArtistPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ import { toast } from "react-toastify";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
faHeadphones,
faMusic,
faPlayCircle,
faUserAstronaut,
} from "@fortawesome/free-solid-svg-icons";
import { chain, isEmpty, isUndefined, partition, sortBy } from "lodash";
import { chain, isEmpty, isUndefined, partition, orderBy } from "lodash";
import { sanitize } from "dompurify";
import {
Link,
Expand All @@ -19,7 +20,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";
Expand All @@ -39,11 +40,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 GetSortingButtons({
anshg1214 marked this conversation as resolved.
Show resolved Hide resolved
sort,
setSort,
}: {
sort: "release_date" | "total_listen_count";
setSort: (sort: "release_date" | "total_listen_count") => void;
}): JSX.Element {
return (
<div className="flex" role="group" aria-label="Sort by">
<Pill
type="secondary"
active={sort === "release_date"}
onClick={() => setSort("release_date")}
>
<FontAwesomeIcon icon={faCalendar} />
</Pill>
<Pill
type="secondary"
active={sort === "total_listen_count"}
onClick={() => setSort("total_listen_count")}
>
<FontAwesomeIcon icon={faMusic} />
anshg1214 marked this conversation as resolved.
Show resolved Hide resolved
</Pill>
</div>
);
}

interface ReleaseGroupWithSecondaryTypes extends ReleaseGroup {
secondary_types: string[];
}

export type ArtistPageProps = {
popularRecordings: PopularRecording[];
artist: MusicBrainzArtist;
releaseGroups: ReleaseGroup[];
releaseGroups: ReleaseGroupWithSecondaryTypes[];
similarArtists: {
artists: SimilarArtist[];
topReleaseGroupColor: ReleaseColor | undefined;
Expand All @@ -53,9 +86,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;
Expand Down Expand Up @@ -84,10 +118,27 @@ export default function ArtistPage(): JSX.Element {
WikipediaExtract
>();

const [albumsByThisArtist, alsoAppearsOn] = partition(
const [sort, setSort] = React.useState<"release_date" | "total_listen_count">(
"release_date"
);

const releaseGroupsSorted = orderBy(
releaseGroups,
(rg) => rg.artists[0].artist_mbid === artist?.artist_mbid
[
"secondary_types",
sort === "release_date" ? (rg) => rg.date || "" : "total_listen_count",
sort === "release_date" ? "total_listen_count" : (rg) => rg.date || "",
"type",
"name",
],
["asc", "desc", "desc", "asc", "asc"]
);

const [rgWithoutCompilations, rgCompilations] = partition(
releaseGroupsSorted,
(rg) => rg?.secondary_types?.includes("Compilation")
);
const rgGroups = chain(rgWithoutCompilations).groupBy("type").value();

React.useEffect(() => {
async function fetchReviews() {
Expand Down Expand Up @@ -407,17 +458,105 @@ export default function ArtistPage(): JSX.Element {
</div>
)}
</div>
<div className="albums full-width scroll-start">
<h3 className="header-with-line">Albums</h3>
<div className="cover-art-container dragscroll">
{albumsByThisArtist.map(getReleaseCard)}
{rgGroups.Album && (
<div className="albums full-width scroll-start">
<div className="listen-header">
<h3 className="header-with-line">Albums</h3>
<GetSortingButtons sort={sort} setSort={setSort} />
</div>
<div
className={`cover-art-container dragscroll ${
rgGroups.Album.length <= COVER_ART_SINGLE_ROW_COUNT
? "single-row"
: ""
}`}
>
{rgGroups.Album.map(getReleaseCard)}
</div>
</div>
</div>
{Boolean(alsoAppearsOn?.length) && (
)}
{rgGroups.Single && (
<div className="albums full-width scroll-start">
<div className="listen-header">
<h3 className="header-with-line">Singles</h3>
<GetSortingButtons sort={sort} setSort={setSort} />
</div>
<div
className={`cover-art-container dragscroll ${
rgGroups.Single.length <= COVER_ART_SINGLE_ROW_COUNT
? "single-row"
: ""
}`}
>
{rgGroups.Single.map(getReleaseCard)}
</div>
</div>
)}
{rgGroups.EP && (
<div className="albums full-width scroll-start">
<div className="listen-header">
<h3 className="header-with-line">EPs</h3>
<GetSortingButtons sort={sort} setSort={setSort} />
</div>
<div
className={`cover-art-container dragscroll ${
rgGroups.EP.length <= COVER_ART_SINGLE_ROW_COUNT
? "single-row"
: ""
}`}
>
{rgGroups.EP.map(getReleaseCard)}
</div>
</div>
)}
{rgGroups.Broadcast && (
<div className="albums full-width scroll-start">
<div className="listen-header">
<h3 className="header-with-line">Broadcasts</h3>
<GetSortingButtons sort={sort} setSort={setSort} />
</div>
<div
className={`cover-art-container dragscroll ${
rgGroups.Broadcast.length <= COVER_ART_SINGLE_ROW_COUNT
? "single-row"
: ""
}`}
>
{rgGroups.Broadcast.map(getReleaseCard)}
</div>
</div>
)}
{rgGroups.Other && (
<div className="albums full-width scroll-start">
<h3 className="header-with-line">Also appears on</h3>
<div className="cover-art-container dragscroll">
{alsoAppearsOn.map(getReleaseCard)}
<div className="listen-header">
<h3 className="header-with-line">Others</h3>
<GetSortingButtons sort={sort} setSort={setSort} />
</div>
<div
className={`cover-art-container dragscroll ${
rgGroups.Other.length <= COVER_ART_SINGLE_ROW_COUNT
? "single-row"
: ""
}`}
>
{rgGroups.Other.map(getReleaseCard)}
</div>
</div>
)}
{rgCompilations && (
<div className="albums full-width scroll-start">
<div className="listen-header">
<h3 className="header-with-line">Compilations</h3>
<GetSortingButtons sort={sort} setSort={setSort} />
</div>
<div
className={`cover-art-container dragscroll ${
rgCompilations.length <= COVER_ART_SINGLE_ROW_COUNT
? "single-row"
: ""
}`}
>
{rgCompilations.map(getReleaseCard)}
</div>
</div>
)}
Expand Down
13 changes: 0 additions & 13 deletions listenbrainz/webserver/views/entity_pages.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = []
Expand Down Expand Up @@ -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 = {
Expand Down