From 7f583394b3f12767b3e770e1f2b16f019a4fa880 Mon Sep 17 00:00:00 2001 From: anshg1214 Date: Thu, 26 Dec 2024 10:34:31 +0000 Subject: [PATCH 01/15] refactor: Improve folder structure and cleanup unused variables --- .../{ => user}/playlists/components/PlaylistCard.tsx | 10 +++++----- .../js/src/user/playlists/components/PlaylistsList.tsx | 6 ++---- 2 files changed, 7 insertions(+), 9 deletions(-) rename frontend/js/src/{ => user}/playlists/components/PlaylistCard.tsx (93%) diff --git a/frontend/js/src/playlists/components/PlaylistCard.tsx b/frontend/js/src/user/playlists/components/PlaylistCard.tsx similarity index 93% rename from frontend/js/src/playlists/components/PlaylistCard.tsx rename to frontend/js/src/user/playlists/components/PlaylistCard.tsx index 7a011d0a8f..5371719fa9 100644 --- a/frontend/js/src/playlists/components/PlaylistCard.tsx +++ b/frontend/js/src/user/playlists/components/PlaylistCard.tsx @@ -9,11 +9,11 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { sanitize } from "dompurify"; import { toast } from "react-toastify"; import { Link } from "react-router-dom"; -import Card from "../../components/Card"; -import { ToastMsg } from "../../notifications/Notifications"; -import GlobalAppContext from "../../utils/GlobalAppContext"; -import PlaylistMenu from "./PlaylistMenu"; -import { getPlaylistExtension, getPlaylistId } from "../utils"; +import Card from "../../../components/Card"; +import { ToastMsg } from "../../../notifications/Notifications"; +import GlobalAppContext from "../../../utils/GlobalAppContext"; +import PlaylistMenu from "../../../playlists/components/PlaylistMenu"; +import { getPlaylistExtension, getPlaylistId } from "../../../playlists/utils"; export type PlaylistCardProps = { playlist: JSPFPlaylist; diff --git a/frontend/js/src/user/playlists/components/PlaylistsList.tsx b/frontend/js/src/user/playlists/components/PlaylistsList.tsx index c832749390..e124642901 100644 --- a/frontend/js/src/user/playlists/components/PlaylistsList.tsx +++ b/frontend/js/src/user/playlists/components/PlaylistsList.tsx @@ -7,7 +7,7 @@ import { toast } from "react-toastify"; import Loader from "../../../components/Loader"; import { ToastMsg } from "../../../notifications/Notifications"; import GlobalAppContext from "../../../utils/GlobalAppContext"; -import PlaylistCard from "../../../playlists/components/PlaylistCard"; +import PlaylistCard from "./PlaylistCard"; import { PlaylistType } from "../../../playlists/utils"; export type PlaylistsListProps = { @@ -49,8 +49,7 @@ export default class PlaylistsList extends React.Component< async componentDidUpdate( prevProps: React.PropsWithChildren ): Promise { - const { user, activeSection } = this.props; - const { currentUser } = this.context; + const { activeSection } = this.props; if (prevProps.activeSection !== activeSection) { await this.fetchPlaylists(0); } @@ -153,7 +152,6 @@ export default class PlaylistsList extends React.Component< onPlaylistDeleted, } = this.props; const { paginationOffset, playlistCount, loading } = this.state; - const { currentUser } = this.context; return (
From f9c035b8b6fb823901690fcd70cd659a3f9c97b7 Mon Sep 17 00:00:00 2001 From: anshg1214 Date: Sat, 28 Dec 2024 17:45:34 +0000 Subject: [PATCH 02/15] feat: Add List view for Playlist Page --- frontend/css/playlists.less | 79 +++++++++++++++++++ frontend/js/src/user/playlists/Playlists.tsx | 57 +++++++++---- .../playlists/components/PlaylistCard.tsx | 71 ++++++++++++++++- .../playlists/components/PlaylistsList.tsx | 8 +- .../js/src/user/playlists/playlistView.d.ts | 6 ++ frontend/js/src/utils/icons.ts | 25 +++++- listenbrainz/webserver/views/user.py | 2 +- 7 files changed, 227 insertions(+), 21 deletions(-) create mode 100644 frontend/js/src/user/playlists/playlistView.d.ts diff --git a/frontend/css/playlists.less b/frontend/css/playlists.less index 10af2f1237..23e94ecd61 100644 --- a/frontend/css/playlists.less +++ b/frontend/css/playlists.less @@ -6,6 +6,10 @@ flex-wrap: wrap; padding-top: 1em; + &.list-view { + flex-direction: column; + } + .playlist { margin: 0.5em; width: 15em; @@ -69,6 +73,81 @@ } } } + + .playlist-card-list-view { + display: flex; + flex-direction: row; + justify-content: space-between; + border: 1px solid #e2e8f0; + padding: 1rem; + margin: 0.5rem 0; + min-height: 8rem; + border-radius: 10px; + + &:hover { + cursor: pointer; + } + + .playlist-index { + min-width: 2rem; + text-align: right; + font-weight: bold; + margin-top: 0.5rem; + } + + .playlist-info { + display: flex; + gap: 2rem; + } + + .playlist-info-content { + display: flex; + flex-direction: column; + margin-bottom: 1rem; + + @media (min-width: 640px) { + margin-bottom: 0; + } + + .playlist-title { + font-size: 1.75rem; + margin-bottom: 0.5rem; + a { + font-weight: 500; + color: #353070; + text-decoration: none; + } + } + } + + .playlist-more-info { + display: flex; + gap: 4rem; + align-self: center; + width: max-content; + + @media (max-width: 640px) { + gap: 2rem; + } + + .playlist-stats { + display: flex; + flex-direction: column; + align-items: flex-end; + + .playlist-date { + display: flex; + color: #6b7280; + gap: 10px; + align-items: baseline; + } + } + + .playlist-actions { + align-self: center; + } + } + } } .new-playlist { diff --git a/frontend/js/src/user/playlists/Playlists.tsx b/frontend/js/src/user/playlists/Playlists.tsx index a407700ed8..0a059f4944 100644 --- a/frontend/js/src/user/playlists/Playlists.tsx +++ b/frontend/js/src/user/playlists/Playlists.tsx @@ -3,7 +3,6 @@ import { faPlusCircle, faUsers, faFileImport, - faMusic, } from "@fortawesome/free-solid-svg-icons"; import { faSpotify, faItunesNote } from "@fortawesome/free-brands-svg-icons"; import * as React from "react"; @@ -29,6 +28,8 @@ import { getPlaylistId, PlaylistType, } from "../../playlists/utils"; +import PlaylistView from "./playlistView.d"; +import { faGrid, faStacked } from "../../utils/icons"; export type UserPlaylistsProps = { playlists: JSPFObject[]; @@ -41,6 +42,7 @@ export type UserPlaylistsState = { playlistCount: number; playlistType: PlaylistType; sortBy: SortOption; + view: PlaylistView; }; enum SortOption { @@ -68,6 +70,7 @@ export default class UserPlaylists extends React.Component< playlistCount, playlistType: PlaylistType.playlists, sortBy: SortOption.DATE_CREATED, + view: PlaylistView.GRID, }; } @@ -196,7 +199,7 @@ export default class UserPlaylists extends React.Component< render() { const { user } = this.props; - const { playlists, playlistCount, playlistType, sortBy } = this.state; + const { playlists, playlistCount, playlistType, sortBy, view } = this.state; const { currentUser } = this.context; return ( @@ -313,20 +316,41 @@ export default class UserPlaylists extends React.Component<
)} -
- Sort by: - +
+
+ Sort by: + +
+
+ View: + this.setState({ view: PlaylistView.GRID })} + title="Grid view" + > + + + this.setState({ view: PlaylistView.LIST })} + title="List view" + > + + +
{this.isCurrentUserPage() && [ void; onPlaylistDeleted: (playlist: JSPFPlaylist) => void; showOptions: boolean; + view: PlaylistView; + index: number; }; export default function PlaylistCard({ playlist, + view, + index, onSuccessfulCopy, onPlaylistEdited, onPlaylistDeleted, @@ -33,6 +44,7 @@ export default function PlaylistCard({ const { APIService, currentUser, spotifyAuth } = React.useContext( GlobalAppContext ); + const navigate = useNavigate(); const playlistId = getPlaylistId(playlist); const customFields = getPlaylistExtension(playlist); @@ -92,6 +104,61 @@ export default function PlaylistCard({ } }, [currentUser.auth_token, playlistId, APIService, onSuccessfulCopy]); + const navigateToPlaylist = () => { + navigate(`/playlist/${sanitize(playlistId)}/`); + }; + + if (view === PlaylistView.LIST) { + return ( +
{ + if (e.key === "Enter") { + navigateToPlaylist(); + } + }} + role="presentation" + > +
+
{index + 1}
+
+
+ + {playlist.title} + +
+ {playlist.annotation && ( +
+ )} +
+
+
+
+
+ + {new Date(playlist.date).toLocaleString(undefined, { + dateStyle: "short", + })} +
+
+ + {playlist.track?.length} track + {playlist.track?.length === 1 ? "" : "s"} +
+
+
+ +
+
+
+ ); + } + return ( {!showOptions ? ( diff --git a/frontend/js/src/user/playlists/components/PlaylistsList.tsx b/frontend/js/src/user/playlists/components/PlaylistsList.tsx index e124642901..3dcd0ff3b2 100644 --- a/frontend/js/src/user/playlists/components/PlaylistsList.tsx +++ b/frontend/js/src/user/playlists/components/PlaylistsList.tsx @@ -9,6 +9,7 @@ import { ToastMsg } from "../../../notifications/Notifications"; import GlobalAppContext from "../../../utils/GlobalAppContext"; import PlaylistCard from "./PlaylistCard"; import { PlaylistType } from "../../../playlists/utils"; +import PlaylistView from "../playlistView.d"; export type PlaylistsListProps = { playlists: JSPFPlaylist[]; @@ -16,6 +17,7 @@ export type PlaylistsListProps = { paginationOffset?: number; playlistCount: number; activeSection: PlaylistType; + view: PlaylistView; onCopiedPlaylist?: (playlist: JSPFPlaylist) => void; onPlaylistEdited: (playlist: JSPFPlaylist) => void; onPlaylistDeleted: (playlist: JSPFPlaylist) => void; @@ -147,6 +149,7 @@ export default class PlaylistsList extends React.Component< playlists, activeSection, children, + view, onCopiedPlaylist, onPlaylistEdited, onPlaylistDeleted, @@ -160,17 +163,20 @@ export default class PlaylistsList extends React.Component< )}
- {playlists.map((playlist: JSPFPlaylist) => { + {playlists.map((playlist: JSPFPlaylist, index: number) => { return ( ); })} diff --git a/frontend/js/src/user/playlists/playlistView.d.ts b/frontend/js/src/user/playlists/playlistView.d.ts new file mode 100644 index 0000000000..07a78ca228 --- /dev/null +++ b/frontend/js/src/user/playlists/playlistView.d.ts @@ -0,0 +1,6 @@ +enum PlaylistView { + LIST = "list", + GRID = "grid", +} + +export default PlaylistView; diff --git a/frontend/js/src/utils/icons.ts b/frontend/js/src/utils/icons.ts index a5303567d5..d90e40d330 100644 --- a/frontend/js/src/utils/icons.ts +++ b/frontend/js/src/utils/icons.ts @@ -4,7 +4,6 @@ import { IconPrefix, } from "@fortawesome/fontawesome-svg-core"; -// eslint-disable-next-line import/prefer-default-export export const faRepeatOnce: IconDefinition = { prefix: "fas" as IconPrefix, iconName: "repeat-one" as IconName, @@ -16,3 +15,27 @@ export const faRepeatOnce: IconDefinition = { "M11 4v1.466a.25.25 0 0 0 .41.192l2.36-1.966a.25.25 0 0 0 0-.384l-2.36-1.966a.25.25 0 0 0-.41.192V3H5a5 5 0 0 0-4.48 7.223.5.5 0 0 0 .896-.446A4 4 0 0 1 5 4zm4.48 1.777a.5.5 0 0 0-.896.446A4 4 0 0 1 11 12H5.001v-1.466a.25.25 0 0 0-.41-.192l-2.36 1.966a.25.25 0 0 0 0 .384l2.36 1.966a.25.25 0 0 0 .41-.192V13h6a5 5 0 0 0 4.48-7.223Z M9 5.5a.5.5 0 0 0-.854-.354l-1.75 1.75a.5.5 0 1 0 .708.708L8 6.707V10.5a.5.5 0 0 0 1 0z", ], }; + +export const faGrid: IconDefinition = { + prefix: "fas" as IconPrefix, + iconName: "grid" as IconName, + icon: [ + 14, + 14, + [], + "", + "M1 2.5A1.5 1.5 0 0 1 2.5 1h3A1.5 1.5 0 0 1 7 2.5v3A1.5 1.5 0 0 1 5.5 7h-3A1.5 1.5 0 0 1 1 5.5zM2.5 2a.5.5 0 0 0-.5.5v3a.5.5 0 0 0 .5.5h3a.5.5 0 0 0 .5-.5v-3a.5.5 0 0 0-.5-.5zm6.5.5A1.5 1.5 0 0 1 10.5 1h3A1.5 1.5 0 0 1 15 2.5v3A1.5 1.5 0 0 1 13.5 7h-3A1.5 1.5 0 0 1 9 5.5zm1.5-.5a.5.5 0 0 0-.5.5v3a.5.5 0 0 0 .5.5h3a.5.5 0 0 0 .5-.5v-3a.5.5 0 0 0-.5-.5zM1 10.5A1.5 1.5 0 0 1 2.5 9h3A1.5 1.5 0 0 1 7 10.5v3A1.5 1.5 0 0 1 5.5 15h-3A1.5 1.5 0 0 1 1 13.5zm1.5-.5a.5.5 0 0 0-.5.5v3a.5.5 0 0 0 .5.5h3a.5.5 0 0 0 .5-.5v-3a.5.5 0 0 0-.5-.5zm6.5.5A1.5 1.5 0 0 1 10.5 9h3a1.5 1.5 0 0 1 1.5 1.5v3a1.5 1.5 0 0 1-1.5 1.5h-3A1.5 1.5 0 0 1 9 13.5zm1.5-.5a.5.5 0 0 0-.5.5v3a.5.5 0 0 0 .5.5h3a.5.5 0 0 0 .5-.5v-3a.5.5 0 0 0-.5-.5z", + ], +}; + +export const faStacked: IconDefinition = { + prefix: "fas" as IconPrefix, + iconName: "list" as IconName, + icon: [ + 14, + 14, + [], + "", + "M3 0h10a2 2 0 0 1 2 2v3a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V2a2 2 0 0 1 2-2m0 1a1 1 0 0 0-1 1v3a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1zm0 8h10a2 2 0 0 1 2 2v3a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2v-3a2 2 0 0 1 2-2m0 1a1 1 0 0 0-1 1v3a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1v-3a1 1 0 0 0-1-1z", + ], +}; diff --git a/listenbrainz/webserver/views/user.py b/listenbrainz/webserver/views/user.py index cb9211524b..a21f19696f 100644 --- a/listenbrainz/webserver/views/user.py +++ b/listenbrainz/webserver/views/user.py @@ -170,7 +170,7 @@ def playlists(user_name: str): playlists = [] user_playlists, playlist_count = get_playlists_for_user( db_conn, ts_conn, user.id, include_private=include_private, - load_recordings=False, count=DEFAULT_NUMBER_OF_PLAYLISTS_PER_CALL, offset=0 + load_recordings=True, count=DEFAULT_NUMBER_OF_PLAYLISTS_PER_CALL, offset=0 ) for playlist in user_playlists: playlists.append(playlist.serialize_jspf()) From b17a422b98ae5f02cd87a499fda310cbb8bfb877 Mon Sep 17 00:00:00 2001 From: anshg1214 Date: Sat, 28 Dec 2024 17:52:09 +0000 Subject: [PATCH 03/15] feat: Improve Button Placement --- frontend/css/playlists.less | 6 ++++ frontend/js/src/user/playlists/Playlists.tsx | 32 ++++++++++---------- 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/frontend/css/playlists.less b/frontend/css/playlists.less index 23e94ecd61..57ff8770c5 100644 --- a/frontend/css/playlists.less +++ b/frontend/css/playlists.less @@ -10,6 +10,12 @@ flex-direction: column; } + .playlist-view-options { + display: flex; + margin-top: 0.8rem; + gap: 1rem; + } + .playlist { margin: 0.5em; width: 15em; diff --git a/frontend/js/src/user/playlists/Playlists.tsx b/frontend/js/src/user/playlists/Playlists.tsx index 0a059f4944..544a3d19c2 100644 --- a/frontend/js/src/user/playlists/Playlists.tsx +++ b/frontend/js/src/user/playlists/Playlists.tsx @@ -316,22 +316,7 @@ export default class UserPlaylists extends React.Component<
)}
-
-
- Sort by: - -
+
View:
+
+ Sort by: + +
Date: Sat, 28 Dec 2024 19:03:52 +0000 Subject: [PATCH 04/15] feat: Add dropdown and improve UI --- frontend/css/playlists.less | 100 +++++++++--------- .../playlists/components/PlaylistCard.tsx | 96 ++++++++++------- 2 files changed, 108 insertions(+), 88 deletions(-) diff --git a/frontend/css/playlists.less b/frontend/css/playlists.less index 57ff8770c5..1c10e26458 100644 --- a/frontend/css/playlists.less +++ b/frontend/css/playlists.less @@ -16,6 +16,33 @@ gap: 1rem; } + /* Pure CSS multiline ellipsis by Sagi Shrieber: + http://hackingui.com/a-pure-css-solution-for-multiline-text-truncation/ + */ + .description { + overflow: hidden; + position: relative; + line-height: 1.2em; + max-height: @descriptionLines * 1.2em; + text-align: justify; + padding-right: 2em; + &:before { + content: "..."; + position: absolute; + right: 1em; + bottom: 0; + } + &:after { + content: ""; + position: absolute; + right: 0; + width: 1em; + height: 1em; + margin-top: 0.2em; + background-color: inherit; + } + } + .playlist { margin: 0.5em; width: 15em; @@ -50,60 +77,44 @@ margin: 0; } } - - /* Pure CSS multiline ellipsis by Sagi Shrieber: - http://hackingui.com/a-pure-css-solution-for-multiline-text-truncation/ - */ - .description { - overflow: hidden; - position: relative; - line-height: 1.2em; - max-height: @descriptionLines * 1.2em; - text-align: justify; - padding-right: 2em; - &:before { - content: "..."; - position: absolute; - right: 1em; - bottom: 0; - } - &:after { - content: ""; - position: absolute; - right: 0; - width: 1em; - height: 1em; - margin-top: 0.2em; - background-color: inherit; - } - } } } .playlist-card-list-view { display: flex; flex-direction: row; - justify-content: space-between; border: 1px solid #e2e8f0; padding: 1rem; margin: 0.5rem 0; min-height: 8rem; border-radius: 10px; + gap: 3rem; - &:hover { - cursor: pointer; + @media (max-width: 640px) { + gap: 0; } - .playlist-index { - min-width: 2rem; - text-align: right; - font-weight: bold; - margin-top: 0.5rem; + &:hover { + cursor: pointer; } - .playlist-info { + .playlist-card-container { display: flex; - gap: 2rem; + justify-content: space-between; + width: 100%; + gap: 0.8rem; + + .playlist-info { + display: flex; + gap: 2rem; + + .playlist-index { + min-width: 2rem; + text-align: right; + font-weight: bold; + margin-top: 0.5rem; + } + } } .playlist-info-content { @@ -128,30 +139,23 @@ .playlist-more-info { display: flex; - gap: 4rem; align-self: center; width: max-content; - @media (max-width: 640px) { - gap: 2rem; - } - .playlist-stats { display: flex; flex-direction: column; align-items: flex-end; .playlist-date { - display: flex; color: #6b7280; - gap: 10px; align-items: baseline; } } - - .playlist-actions { - align-self: center; - } + } + .playlist-actions { + align-self: center; + margin: 1rem; } } } diff --git a/frontend/js/src/user/playlists/components/PlaylistCard.tsx b/frontend/js/src/user/playlists/components/PlaylistCard.tsx index 37528e576a..d170122026 100644 --- a/frontend/js/src/user/playlists/components/PlaylistCard.tsx +++ b/frontend/js/src/user/playlists/components/PlaylistCard.tsx @@ -110,50 +110,66 @@ export default function PlaylistCard({ if (view === PlaylistView.LIST) { return ( -
{ - if (e.key === "Enter") { - navigateToPlaylist(); - } - }} - role="presentation" - > -
-
{index + 1}
-
-
- - {playlist.title} - +
+
{ + if (e.key === "Enter") { + navigateToPlaylist(); + } + }} + role="presentation" + > +
+
{index + 1}
+
+
+ + {playlist.title} + +
+ {playlist.annotation && ( +
+ )}
- {playlist.annotation && ( -
- )}
-
-
-
-
- - {new Date(playlist.date).toLocaleString(undefined, { - dateStyle: "short", - })} -
-
- - {playlist.track?.length} track - {playlist.track?.length === 1 ? "" : "s"} +
+
+
+ + {new Date(playlist.date).toLocaleString(undefined, { + dateStyle: "short", + })} +
+
+ + {playlist.track?.length} track + {playlist.track?.length === 1 ? "" : "s"} +
-
- -
+
+
+ +
); From 5adb896882ed7679c41be1083e59d861ebdd3835 Mon Sep 17 00:00:00 2001 From: anshg1214 Date: Sat, 28 Dec 2024 19:21:54 +0000 Subject: [PATCH 05/15] feat: add gap between view options --- frontend/css/playlists.less | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/frontend/css/playlists.less b/frontend/css/playlists.less index 1c10e26458..498d286e0e 100644 --- a/frontend/css/playlists.less +++ b/frontend/css/playlists.less @@ -1,6 +1,19 @@ @playlistBackgroundColor: #fafafa; @descriptionLines: 3; +#playlists-page .playlist-view-options { + display: flex; + flex-direction: column; + margin-top: 0.8rem; + gap: 1rem; + + .playlist-sort-controls { + display: flex; + align-items: center; + gap: 1rem; + } +} + #playlists-container { display: flex; flex-wrap: wrap; @@ -10,12 +23,6 @@ flex-direction: column; } - .playlist-view-options { - display: flex; - margin-top: 0.8rem; - gap: 1rem; - } - /* Pure CSS multiline ellipsis by Sagi Shrieber: http://hackingui.com/a-pure-css-solution-for-multiline-text-truncation/ */ @@ -109,11 +116,11 @@ gap: 2rem; .playlist-index { - min-width: 2rem; - text-align: right; - font-weight: bold; - margin-top: 0.5rem; - } + min-width: 2rem; + text-align: right; + font-weight: bold; + margin-top: 0.5rem; + } } } @@ -226,10 +233,3 @@ align-self: center; min-width: 3em; } - -.playlist-sort-controls { - display: flex; - align-items: center; - gap: 1rem; - margin-top: 0.8em; -} From 12a85f933783a625ccf0ea6c25bd3d5d1af9d6ae Mon Sep 17 00:00:00 2001 From: anshg1214 Date: Sat, 28 Dec 2024 19:54:44 +0000 Subject: [PATCH 06/15] feat: Cleanup the pagination logic --- frontend/js/src/user/playlists/Playlists.tsx | 58 ++++++- .../playlists/components/PlaylistsList.tsx | 147 ++---------------- listenbrainz/webserver/views/user.py | 10 +- 3 files changed, 75 insertions(+), 140 deletions(-) diff --git a/frontend/js/src/user/playlists/Playlists.tsx b/frontend/js/src/user/playlists/Playlists.tsx index 544a3d19c2..12754836ba 100644 --- a/frontend/js/src/user/playlists/Playlists.tsx +++ b/frontend/js/src/user/playlists/Playlists.tsx @@ -11,7 +11,7 @@ import { orderBy } from "lodash"; import NiceModal from "@ebay/nice-modal-react"; import { IconProp } from "@fortawesome/fontawesome-svg-core"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { useLoaderData } from "react-router-dom"; +import { useLoaderData, useSearchParams } from "react-router-dom"; import { toast } from "react-toastify"; import { Helmet } from "react-helmet"; import Card from "../../components/Card"; @@ -35,6 +35,7 @@ export type UserPlaylistsProps = { playlists: JSPFObject[]; user: ListenBrainzUser; playlistCount: number; + pageCount: number; }; export type UserPlaylistsState = { @@ -55,14 +56,20 @@ enum SortOption { type UserPlaylistsLoaderData = UserPlaylistsProps; +type UserPlaylistsClassProps = UserPlaylistsProps & { + page: number; + handleClickPrevious: () => void; + handleClickNext: () => void; +}; + export default class UserPlaylists extends React.Component< - UserPlaylistsProps, + UserPlaylistsClassProps, UserPlaylistsState > { static contextType = GlobalAppContext; declare context: React.ContextType; - constructor(props: UserPlaylistsProps) { + constructor(props: UserPlaylistsClassProps) { super(props); const { playlists, playlistCount } = props; this.state = { @@ -74,6 +81,13 @@ export default class UserPlaylists extends React.Component< }; } + componentDidUpdate(prevProps: Readonly): void { + const { playlists } = this.props; + if (prevProps.playlists !== playlists) { + this.setState({ playlists: playlists.map((pl) => pl.playlist) }); + } + } + alertNotAuthorized = () => { toast.error(
{this.isCurrentUserPage() && [ ; + const [searchParams, setSearchParams] = useSearchParams(); + const currPageNoStr = searchParams.get("page") || "1"; + const currPageNo = parseInt(currPageNoStr, 10); + + const handleClickPrevious = () => { + setSearchParams({ + page: Math.max(currPageNo - 1, 1).toString(), + }); + }; + + const handleClickNext = () => { + setSearchParams({ + page: Math.min(currPageNo + 1, data.pageCount).toString(), + }); + }; + return ( + + ); } diff --git a/frontend/js/src/user/playlists/components/PlaylistsList.tsx b/frontend/js/src/user/playlists/components/PlaylistsList.tsx index 3dcd0ff3b2..928c219f8f 100644 --- a/frontend/js/src/user/playlists/components/PlaylistsList.tsx +++ b/frontend/js/src/user/playlists/components/PlaylistsList.tsx @@ -10,18 +10,20 @@ import GlobalAppContext from "../../../utils/GlobalAppContext"; import PlaylistCard from "./PlaylistCard"; import { PlaylistType } from "../../../playlists/utils"; import PlaylistView from "../playlistView.d"; +import Pagination from "../../../common/Pagination"; export type PlaylistsListProps = { playlists: JSPFPlaylist[]; user: ListenBrainzUser; - paginationOffset?: number; - playlistCount: number; + pageCount: number; + page: number; activeSection: PlaylistType; view: PlaylistView; onCopiedPlaylist?: (playlist: JSPFPlaylist) => void; onPlaylistEdited: (playlist: JSPFPlaylist) => void; onPlaylistDeleted: (playlist: JSPFPlaylist) => void; - onPaginatePlaylists: (playlists: JSPFPlaylist[]) => void; + handleClickPrevious: () => void; + handleClickNext: () => void; }; export type PlaylistsListState = { @@ -31,32 +33,11 @@ export type PlaylistsListState = { }; export default class PlaylistsList extends React.Component< - React.PropsWithChildren, - PlaylistsListState + React.PropsWithChildren > { static contextType = GlobalAppContext; declare context: React.ContextType; - private DEFAULT_PLAYLISTS_PER_PAGE = 25; - - constructor(props: React.PropsWithChildren) { - super(props); - this.state = { - loading: false, - paginationOffset: props.paginationOffset || 0, - playlistCount: props.playlistCount, - }; - } - - async componentDidUpdate( - prevProps: React.PropsWithChildren - ): Promise { - const { activeSection } = this.props; - if (prevProps.activeSection !== activeSection) { - await this.fetchPlaylists(0); - } - } - alertNotAuthorized = () => { toast.error( { - const { paginationOffset, playlistCount } = this.state; - const newOffset = paginationOffset + this.DEFAULT_PLAYLISTS_PER_PAGE; - // No more playlists to fetch - if (newOffset >= playlistCount) { - return; - } - await this.fetchPlaylists(newOffset); - }; - - handleClickPrevious = async () => { - const { paginationOffset } = this.state; - // No more playlists to fetch - if (paginationOffset === 0) { - return; - } - const newOffset = Math.max( - 0, - paginationOffset - this.DEFAULT_PLAYLISTS_PER_PAGE - ); - await this.fetchPlaylists(newOffset); - }; - - handleAPIResponse = (newPlaylists: { - playlists: JSPFObject[]; - playlist_count: number; - count: string; - offset: string; - }) => { - const { onPaginatePlaylists } = this.props; - const parsedOffset = parseInt(newPlaylists.offset, 10); - this.setState({ - playlistCount: newPlaylists.playlist_count, - paginationOffset: parsedOffset, - loading: false, - }); - onPaginatePlaylists( - newPlaylists.playlists.map((pl: JSPFObject) => pl.playlist) - ); - }; - - fetchPlaylists = async (newOffset: number = 0) => { - const { APIService, currentUser } = this.context; - const { user, activeSection } = this.props; - this.setState({ loading: true }); - try { - const newPlaylists = await APIService.getUserPlaylists( - user.name, - currentUser?.auth_token, - newOffset, - this.DEFAULT_PLAYLISTS_PER_PAGE, - activeSection === PlaylistType.recommendations, - activeSection === PlaylistType.collaborations - ); - - this.handleAPIResponse(newPlaylists); - } catch (error) { - toast.error( - , - { toastId: "load-playlists-error" } - ); - this.setState({ loading: false }); - } - }; - render() { const { playlists, activeSection, children, view, + page, + pageCount, onCopiedPlaylist, onPlaylistEdited, onPlaylistDeleted, + handleClickPrevious, + handleClickNext, } = this.props; - const { paginationOffset, playlistCount, loading } = this.state; return (
- {!playlists.length && (

No playlists to show yet. Come back later !

)}
{playlists.map((playlist: JSPFPlaylist, index: number) => { return ( @@ -182,41 +96,12 @@ export default class PlaylistsList extends React.Component< })} {children}
- +
); } diff --git a/listenbrainz/webserver/views/user.py b/listenbrainz/webserver/views/user.py index a21f19696f..1b9d416edf 100644 --- a/listenbrainz/webserver/views/user.py +++ b/listenbrainz/webserver/views/user.py @@ -1,4 +1,5 @@ from datetime import datetime +from math import ceil import listenbrainz.db.user as db_user import listenbrainz.db.user_relationship as db_user_relationship @@ -17,7 +18,7 @@ from listenbrainz.webserver.errors import APIBadRequest from listenbrainz.webserver.login import User, api_login_required from listenbrainz.webserver.views.api import DEFAULT_NUMBER_OF_PLAYLISTS_PER_CALL -from werkzeug.exceptions import NotFound +from listenbrainz.webserver.views.api_tools import get_non_negative_param LISTENS_PER_PAGE = 25 DEFAULT_NUMBER_OF_FEEDBACK_ITEMS_PER_CALL = 25 @@ -156,6 +157,8 @@ def stats(user_name: str): def playlists(user_name: str): """ Show user playlists """ + page = get_non_negative_param("page", default=1) + user = _get_user(user_name) if not user: return jsonify({"error": "Cannot find user: %s" % user_name}), 404 @@ -168,9 +171,11 @@ def playlists(user_name: str): include_private = current_user.is_authenticated and current_user.id == user.id playlists = [] + offset = (page - 1) * DEFAULT_NUMBER_OF_PLAYLISTS_PER_CALL + user_playlists, playlist_count = get_playlists_for_user( db_conn, ts_conn, user.id, include_private=include_private, - load_recordings=True, count=DEFAULT_NUMBER_OF_PLAYLISTS_PER_CALL, offset=0 + load_recordings=True, count=DEFAULT_NUMBER_OF_PLAYLISTS_PER_CALL, offset=offset ) for playlist in user_playlists: playlists.append(playlist.serialize_jspf()) @@ -179,6 +184,7 @@ def playlists(user_name: str): "playlists": playlists, "user": user_data, "playlistCount": playlist_count, + "pageCount": ceil(playlist_count / DEFAULT_NUMBER_OF_PLAYLISTS_PER_CALL), "logged_in_user_follows_user": logged_in_user_follows_user(user), } From 8abe532aa0adbbaceb0fc1ae478d7cdec45d93c2 Mon Sep 17 00:00:00 2001 From: anshg1214 Date: Sat, 28 Dec 2024 20:01:32 +0000 Subject: [PATCH 07/15] feat: Migrate from Class Component to Functional Component --- frontend/js/src/user/playlists/Playlists.tsx | 2 +- .../playlists/components/PlaylistsList.tsx | 125 +++++++----------- 2 files changed, 47 insertions(+), 80 deletions(-) diff --git a/frontend/js/src/user/playlists/Playlists.tsx b/frontend/js/src/user/playlists/Playlists.tsx index 12754836ba..16483fccde 100644 --- a/frontend/js/src/user/playlists/Playlists.tsx +++ b/frontend/js/src/user/playlists/Playlists.tsx @@ -376,7 +376,6 @@ export default class UserPlaylists extends React.Component< onCopiedPlaylist={this.onCopiedPlaylist} playlists={playlists} activeSection={playlistType} - user={user} onPlaylistEdited={this.onPlaylistEdited} onPlaylistDeleted={this.onPlaylistDeleted} view={view} @@ -428,6 +427,7 @@ export function UserPlaylistsWrapper() { page: Math.min(currPageNo + 1, data.pageCount).toString(), }); }; + return ( -> { - static contextType = GlobalAppContext; - declare context: React.ContextType; - - alertNotAuthorized = () => { - toast.error( - , - { toastId: "auth-error" } - ); - }; - - isCurrentUserPage = () => { - const { user, activeSection } = this.props; - const { currentUser } = this.context; - if (activeSection === PlaylistType.recommendations) { - return false; - } - return currentUser?.name === user.name; - }; +export default function PlaylistsList( + props: PlaylistsListProps & { children: React.ReactNode } +) { + const { + playlists, + activeSection, + children, + view, + page, + pageCount, + onCopiedPlaylist, + onPlaylistEdited, + onPlaylistDeleted, + handleClickPrevious, + handleClickNext, + } = props; - render() { - const { - playlists, - activeSection, - children, - view, - page, - pageCount, - onCopiedPlaylist, - onPlaylistEdited, - onPlaylistDeleted, - handleClickPrevious, - handleClickNext, - } = this.props; - return ( -
- {!playlists.length && ( -

No playlists to show yet. Come back later !

- )} -
- {playlists.map((playlist: JSPFPlaylist, index: number) => { - return ( - - ); - })} - {children} -
- + return ( +
+ {!playlists.length &&

No playlists to show yet. Come back later !

} +
+ {playlists.map((playlist: JSPFPlaylist, index: number) => { + return ( + + ); + })} + {children}
- ); - } + +
+ ); } From 1c4c4a534da6a2c5199a75fe3458997a469c66d5 Mon Sep 17 00:00:00 2001 From: anshg1214 Date: Sat, 28 Dec 2024 20:01:52 +0000 Subject: [PATCH 08/15] feat: Change flex direction --- frontend/css/playlists.less | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/frontend/css/playlists.less b/frontend/css/playlists.less index 498d286e0e..9c95cfb364 100644 --- a/frontend/css/playlists.less +++ b/frontend/css/playlists.less @@ -3,7 +3,6 @@ #playlists-page .playlist-view-options { display: flex; - flex-direction: column; margin-top: 0.8rem; gap: 1rem; @@ -145,21 +144,21 @@ } .playlist-more-info { - display: flex; align-self: center; - width: max-content; .playlist-stats { display: flex; flex-direction: column; - align-items: flex-end; .playlist-date { color: #6b7280; - align-items: baseline; + display: flex; + align-items: center; + gap: 8px; } } } + .playlist-actions { align-self: center; margin: 1rem; From 5579c34ddfc28c23d8696aaa995f0b4f9eb70d3c Mon Sep 17 00:00:00 2001 From: anshg1214 Date: Sat, 28 Dec 2024 20:22:49 +0000 Subject: [PATCH 09/15] feat: Improve new playlist button --- frontend/css/playlists.less | 5 +++++ frontend/js/src/user/playlists/Playlists.tsx | 4 +++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/frontend/css/playlists.less b/frontend/css/playlists.less index 9c95cfb364..08ce41aea0 100644 --- a/frontend/css/playlists.less +++ b/frontend/css/playlists.less @@ -176,6 +176,11 @@ height: initial !important; color: white; text-align: center; + + &.list-view { + width: auto; + } + > div { display: flex; flex-direction: column; diff --git a/frontend/js/src/user/playlists/Playlists.tsx b/frontend/js/src/user/playlists/Playlists.tsx index 16483fccde..507273eb82 100644 --- a/frontend/js/src/user/playlists/Playlists.tsx +++ b/frontend/js/src/user/playlists/Playlists.tsx @@ -387,7 +387,9 @@ export default class UserPlaylists extends React.Component< {this.isCurrentUserPage() && [ { From 823ebdfa89f323ced8b59a757d82036a6f7b0670 Mon Sep 17 00:00:00 2001 From: anshg1214 Date: Sat, 28 Dec 2024 21:17:50 +0000 Subject: [PATCH 10/15] feat: Improve collaborative playlist toggle --- frontend/js/src/user/playlists/Playlists.tsx | 43 ++++++++++++++----- .../playlists/components/PlaylistCard.tsx | 29 ++++++++++--- listenbrainz/webserver/views/user.py | 17 +++++--- 3 files changed, 67 insertions(+), 22 deletions(-) diff --git a/frontend/js/src/user/playlists/Playlists.tsx b/frontend/js/src/user/playlists/Playlists.tsx index 507273eb82..4b0dc6cb5a 100644 --- a/frontend/js/src/user/playlists/Playlists.tsx +++ b/frontend/js/src/user/playlists/Playlists.tsx @@ -30,6 +30,7 @@ import { } from "../../playlists/utils"; import PlaylistView from "./playlistView.d"; import { faGrid, faStacked } from "../../utils/icons"; +import { getObjectForURLSearchParams } from "../../utils/utils"; export type UserPlaylistsProps = { playlists: JSPFObject[]; @@ -40,8 +41,6 @@ export type UserPlaylistsProps = { export type UserPlaylistsState = { playlists: JSPFPlaylist[]; - playlistCount: number; - playlistType: PlaylistType; sortBy: SortOption; view: PlaylistView; }; @@ -58,8 +57,10 @@ type UserPlaylistsLoaderData = UserPlaylistsProps; type UserPlaylistsClassProps = UserPlaylistsProps & { page: number; + playlistType: PlaylistType; handleClickPrevious: () => void; handleClickNext: () => void; + handleSetPlaylistType: (newType: PlaylistType) => void; }; export default class UserPlaylists extends React.Component< @@ -74,8 +75,6 @@ export default class UserPlaylists extends React.Component< const { playlists, playlistCount } = props; this.state = { playlists: playlists?.map((pl) => pl.playlist) ?? [], - playlistCount, - playlistType: PlaylistType.playlists, sortBy: SortOption.DATE_CREATED, view: PlaylistView.GRID, }; @@ -98,16 +97,16 @@ export default class UserPlaylists extends React.Component< ); }; - updatePlaylists = (playlists: JSPFPlaylist[]): void => { - this.setState({ playlists }); - }; - setPlaylistType = (type: PlaylistType) => { - this.setState({ playlistType: type, sortBy: SortOption.DATE_CREATED }); + const { handleSetPlaylistType, playlistType } = this.props; + if (type !== playlistType) { + handleSetPlaylistType(type); + this.setState({ sortBy: SortOption.DATE_CREATED }); + } }; onCopiedPlaylist = (newPlaylist: JSPFPlaylist): void => { - const { playlistType } = this.state; + const { playlistType } = this.props; if (this.isCurrentUserPage() && playlistType === PlaylistType.playlists) { this.setState((prevState) => ({ playlists: [newPlaylist, ...prevState.playlists], @@ -216,10 +215,11 @@ export default class UserPlaylists extends React.Component< user, pageCount, page, + playlistType, handleClickPrevious, handleClickNext, } = this.props; - const { playlists, playlistCount, playlistType, sortBy, view } = this.state; + const { playlists, sortBy, view } = this.state; const { currentUser } = this.context; return ( @@ -415,27 +415,48 @@ export default class UserPlaylists extends React.Component< export function UserPlaylistsWrapper() { const data = useLoaderData() as UserPlaylistsLoaderData; const [searchParams, setSearchParams] = useSearchParams(); + const searchParamsObj = getObjectForURLSearchParams(searchParams); const currPageNoStr = searchParams.get("page") || "1"; const currPageNo = parseInt(currPageNoStr, 10); + const type = searchParams.get("type") || ""; const handleClickPrevious = () => { setSearchParams({ + ...searchParamsObj, page: Math.max(currPageNo - 1, 1).toString(), }); }; const handleClickNext = () => { setSearchParams({ + ...searchParamsObj, page: Math.min(currPageNo + 1, data.pageCount).toString(), }); }; + const playlistType = + type === "collaborative" + ? PlaylistType.collaborations + : PlaylistType.playlists; + + const handleSetPlaylistType = (newType: PlaylistType) => { + const newParams = { ...searchParamsObj }; + if (newType === PlaylistType.collaborations) { + newParams.type = "collaborative"; + } else { + delete newParams?.type; + } + setSearchParams(newParams); + }; + return ( ); } diff --git a/frontend/js/src/user/playlists/components/PlaylistCard.tsx b/frontend/js/src/user/playlists/components/PlaylistCard.tsx index d170122026..63a883b64e 100644 --- a/frontend/js/src/user/playlists/components/PlaylistCard.tsx +++ b/frontend/js/src/user/playlists/components/PlaylistCard.tsx @@ -164,12 +164,29 @@ export default function PlaylistCard({ aria-expanded="true" type="button" /> - + {showOptions ? ( + + ) : ( + + )}
); diff --git a/listenbrainz/webserver/views/user.py b/listenbrainz/webserver/views/user.py index 1b9d416edf..108c017d4d 100644 --- a/listenbrainz/webserver/views/user.py +++ b/listenbrainz/webserver/views/user.py @@ -9,7 +9,7 @@ from listenbrainz import webserver from listenbrainz.db.msid_mbid_mapping import fetch_track_metadata_for_items -from listenbrainz.db.playlist import get_playlists_for_user, get_recommendation_playlists_for_user +from listenbrainz.db.playlist import get_playlists_for_user, get_recommendation_playlists_for_user, get_playlists_collaborated_on from listenbrainz.db.pinned_recording import get_current_pin_for_user, get_pin_count_for_user, get_pin_history_for_user from listenbrainz.db.feedback import get_feedback_count_for_user, get_feedback_for_user from listenbrainz.db import year_in_music as db_year_in_music @@ -158,6 +158,7 @@ def playlists(user_name: str): """ Show user playlists """ page = get_non_negative_param("page", default=1) + type = request.args.get("type", "") user = _get_user(user_name) if not user: @@ -173,10 +174,16 @@ def playlists(user_name: str): playlists = [] offset = (page - 1) * DEFAULT_NUMBER_OF_PLAYLISTS_PER_CALL - user_playlists, playlist_count = get_playlists_for_user( - db_conn, ts_conn, user.id, include_private=include_private, - load_recordings=True, count=DEFAULT_NUMBER_OF_PLAYLISTS_PER_CALL, offset=offset - ) + if type == "collaborative": + user_playlists, playlist_count = get_playlists_collaborated_on( + db_conn, ts_conn, user.id, include_private=include_private, + load_recordings=True, count=DEFAULT_NUMBER_OF_PLAYLISTS_PER_CALL, offset=offset + ) + else: + user_playlists, playlist_count = get_playlists_for_user( + db_conn, ts_conn, user.id, include_private=include_private, + load_recordings=True, count=DEFAULT_NUMBER_OF_PLAYLISTS_PER_CALL, offset=offset + ) for playlist in user_playlists: playlists.append(playlist.serialize_jspf()) From ec93ed68465bab03200c6fda3204046804f5513c Mon Sep 17 00:00:00 2001 From: anshg1214 Date: Mon, 30 Dec 2024 10:18:10 +0000 Subject: [PATCH 11/15] feat: Improve Playlist Description --- frontend/css/playlists.less | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/frontend/css/playlists.less b/frontend/css/playlists.less index 08ce41aea0..e93a1dbb45 100644 --- a/frontend/css/playlists.less +++ b/frontend/css/playlists.less @@ -32,11 +32,17 @@ max-height: @descriptionLines * 1.2em; text-align: justify; padding-right: 2em; + display: -webkit-box; + line-clamp: @descriptionLines; + -webkit-line-clamp: @descriptionLines; + -webkit-box-orient: vertical; + &:before { content: "..."; position: absolute; right: 1em; bottom: 0; + display: none; } &:after { content: ""; From 68627ea84f0f24f15909a66cab68ac78b8cce268 Mon Sep 17 00:00:00 2001 From: anshg1214 Date: Sat, 4 Jan 2025 17:58:26 +0000 Subject: [PATCH 12/15] feat: Improve Playlist page navbar --- frontend/js/src/user/playlists/Playlists.tsx | 277 ++++++++++--------- 1 file changed, 141 insertions(+), 136 deletions(-) diff --git a/frontend/js/src/user/playlists/Playlists.tsx b/frontend/js/src/user/playlists/Playlists.tsx index 4b0dc6cb5a..5d61097ca5 100644 --- a/frontend/js/src/user/playlists/Playlists.tsx +++ b/frontend/js/src/user/playlists/Playlists.tsx @@ -230,146 +230,151 @@ export default class UserPlaylists extends React.Component< } Playlists`}
-
- this.setPlaylistType(PlaylistType.playlists)} - > - Playlists - - this.setPlaylistType(PlaylistType.collaborations)} - > - Collaborative - -
- {this.isCurrentUserPage() && ( -
- -
    Playlists + + + this.setPlaylistType(PlaylistType.collaborations) + } > -
  • - -
  • -
  • - -
  • -
  • - -
  • -
+ Collaborative + +
+
+ this.setState({ view: PlaylistView.GRID })} + title="Grid view" + > + + + this.setState({ view: PlaylistView.LIST })} + title="List view" + > + +
- )} -
-
-
- View: - this.setState({ view: PlaylistView.GRID })} - title="Grid view" - > - - - this.setState({ view: PlaylistView.LIST })} - title="List view" - > - -
-
- Sort by: - +
+
+ Sort by: + +
+ {this.isCurrentUserPage() && ( +
+ +
    +
  • + +
  • +
  • + +
  • +
  • + +
  • +
+
+ )}
Date: Wed, 8 Jan 2025 18:11:15 +0000 Subject: [PATCH 13/15] feat: Improve Playlist List view --- frontend/css/playlists.less | 22 +++++----------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/frontend/css/playlists.less b/frontend/css/playlists.less index e93a1dbb45..076a14b1d0 100644 --- a/frontend/css/playlists.less +++ b/frontend/css/playlists.less @@ -34,24 +34,12 @@ padding-right: 2em; display: -webkit-box; line-clamp: @descriptionLines; + margin-top: 0.5rem; -webkit-line-clamp: @descriptionLines; -webkit-box-orient: vertical; - &:before { - content: "..."; - position: absolute; - right: 1em; - bottom: 0; - display: none; - } - &:after { - content: ""; - position: absolute; - right: 0; - width: 1em; - height: 1em; - margin-top: 0.2em; - background-color: inherit; + p { + margin-bottom: 0; } } @@ -118,13 +106,13 @@ .playlist-info { display: flex; + align-items: center; gap: 2rem; .playlist-index { min-width: 2rem; text-align: right; font-weight: bold; - margin-top: 0.5rem; } } } @@ -140,7 +128,6 @@ .playlist-title { font-size: 1.75rem; - margin-bottom: 0.5rem; a { font-weight: 500; color: #353070; @@ -151,6 +138,7 @@ .playlist-more-info { align-self: center; + flex-shrink: 0; .playlist-stats { display: flex; From 2e877a0d1fed8ef7d63ee39449565f064b4a09d6 Mon Sep 17 00:00:00 2001 From: anshg1214 Date: Mon, 13 Jan 2025 17:29:32 +0000 Subject: [PATCH 14/15] feat: Improve Icon width and index --- frontend/js/src/user/playlists/Playlists.tsx | 4 ++-- frontend/js/src/user/playlists/components/PlaylistCard.tsx | 4 +++- frontend/js/src/user/playlists/components/PlaylistsList.tsx | 1 + 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/frontend/js/src/user/playlists/Playlists.tsx b/frontend/js/src/user/playlists/Playlists.tsx index 5d61097ca5..a905faf130 100644 --- a/frontend/js/src/user/playlists/Playlists.tsx +++ b/frontend/js/src/user/playlists/Playlists.tsx @@ -256,7 +256,7 @@ export default class UserPlaylists extends React.Component< onClick={() => this.setState({ view: PlaylistView.GRID })} title="Grid view" > - + this.setState({ view: PlaylistView.LIST })} title="List view" > - +
diff --git a/frontend/js/src/user/playlists/components/PlaylistCard.tsx b/frontend/js/src/user/playlists/components/PlaylistCard.tsx index 63a883b64e..ad4862e571 100644 --- a/frontend/js/src/user/playlists/components/PlaylistCard.tsx +++ b/frontend/js/src/user/playlists/components/PlaylistCard.tsx @@ -29,6 +29,7 @@ export type PlaylistCardProps = { onPlaylistDeleted: (playlist: JSPFPlaylist) => void; showOptions: boolean; view: PlaylistView; + page: number; index: number; }; @@ -36,6 +37,7 @@ export default function PlaylistCard({ playlist, view, index, + page, onSuccessfulCopy, onPlaylistEdited, onPlaylistDeleted, @@ -122,7 +124,7 @@ export default function PlaylistCard({ role="presentation" >
-
{index + 1}
+
{index + 1 + (page - 1) * 25}
diff --git a/frontend/js/src/user/playlists/components/PlaylistsList.tsx b/frontend/js/src/user/playlists/components/PlaylistsList.tsx index 99709476e2..06619d42d9 100644 --- a/frontend/js/src/user/playlists/components/PlaylistsList.tsx +++ b/frontend/js/src/user/playlists/components/PlaylistsList.tsx @@ -52,6 +52,7 @@ export default function PlaylistsList( return ( Date: Mon, 13 Jan 2025 18:07:55 +0000 Subject: [PATCH 15/15] feat: Simplify index --- frontend/js/src/user/playlists/components/PlaylistCard.tsx | 4 +--- frontend/js/src/user/playlists/components/PlaylistsList.tsx | 3 +-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/frontend/js/src/user/playlists/components/PlaylistCard.tsx b/frontend/js/src/user/playlists/components/PlaylistCard.tsx index ad4862e571..63a883b64e 100644 --- a/frontend/js/src/user/playlists/components/PlaylistCard.tsx +++ b/frontend/js/src/user/playlists/components/PlaylistCard.tsx @@ -29,7 +29,6 @@ export type PlaylistCardProps = { onPlaylistDeleted: (playlist: JSPFPlaylist) => void; showOptions: boolean; view: PlaylistView; - page: number; index: number; }; @@ -37,7 +36,6 @@ export default function PlaylistCard({ playlist, view, index, - page, onSuccessfulCopy, onPlaylistEdited, onPlaylistDeleted, @@ -124,7 +122,7 @@ export default function PlaylistCard({ role="presentation" >
-
{index + 1 + (page - 1) * 25}
+
{index + 1}
diff --git a/frontend/js/src/user/playlists/components/PlaylistsList.tsx b/frontend/js/src/user/playlists/components/PlaylistsList.tsx index 06619d42d9..db22f56029 100644 --- a/frontend/js/src/user/playlists/components/PlaylistsList.tsx +++ b/frontend/js/src/user/playlists/components/PlaylistsList.tsx @@ -52,14 +52,13 @@ export default function PlaylistsList( return ( ); })}