Skip to content

Commit 094190a

Browse files
committed
fix finding associated playlists
1 parent 71ae72e commit 094190a

File tree

8 files changed

+93
-132
lines changed

8 files changed

+93
-132
lines changed

backend/src/controllers/music_data.py

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
get_playlist_track_list,
88
get_recent_user_playlists,
99
get_user_playlists,
10+
search_playlist_names,
1011
update_playlist_info,
1112
)
1213
from src.dataclasses.playlist import Playlist
@@ -105,18 +106,11 @@ def get_playlist_tracks(id):
105106
)
106107
]
107108

108-
@music_controller.route("find_associated_playlists", methods=["POST"])
109+
@music_controller.route("playlist/search", methods=["POST"])
109110
def find_associated_playlists():
110-
access_token = request.cookies.get("spotify_access_token")
111111
user_id = request.cookies.get("user_id")
112-
playlist = Playlist.model_validate(request.json)
113-
associated_playlists = spotify.find_associated_playlists(
114-
user_id=user_id, access_token=access_token, playlist=playlist
115-
)
116-
return [
117-
associated_playlist.model_dump()
118-
for associated_playlist in associated_playlists
119-
]
112+
search = request.json
113+
return search_playlist_names(user_id, search)
120114

121115
@music_controller.route("playback", methods=["GET"])
122116
def get_playback_info():

backend/src/database/crud/playlist.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,3 +303,28 @@ def get_playlist_duration_up_to_track(playlist_id, track_id):
303303
if track.id == track_id:
304304
break
305305
return total_duration
306+
307+
308+
def search_playlist_names(user_id: str, search: str) -> List[dict]:
309+
# Query playlists where name ends with the given date_str
310+
playlists = (
311+
DbPlaylist.select(
312+
DbPlaylist.id, DbPlaylist.name, DbPlaylist.description, DbPlaylist.image_url
313+
)
314+
.where(DbPlaylist.name.contains(search))
315+
.where(DbPlaylist.user_id == user_id)
316+
.execute()
317+
)
318+
319+
# Build a list of playlist details
320+
result = [
321+
{
322+
"id": playlist.id,
323+
"name": playlist.name,
324+
"description": playlist.description,
325+
"image_url": playlist.image_url,
326+
}
327+
for playlist in playlists
328+
]
329+
330+
return result

frontend/src/api/index.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -114,13 +114,13 @@ export const getPlaybackInfo = async (): Promise<PlaybackInfo> => {
114114
return jsonRequest(`music/playback`, RequestMethod.GET, undefined, false);
115115
};
116116

117-
export const findAssociatedPlaylists = async (
118-
playlist: Playlist,
117+
export const playlistSearch = async (
118+
search: string,
119119
): Promise<Playlist[]> => {
120120
return jsonRequest(
121-
`spotify/find_associated_playlists/${playlist.id}`,
121+
`music/playlist/search`,
122122
RequestMethod.POST,
123-
playlist,
123+
search,
124124
);
125125
};
126126

frontend/src/components/Carousel/Carousel.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,11 @@ const Slide: FC<SlideProps> = ({children}) => {
1212

1313
interface CarouselProps {
1414
slides: ReactNode[]
15+
startIndex?: number
1516
}
1617

17-
const Carousel: FC<CarouselProps> = ({slides}) => {
18-
const [emblaRef] = useEmblaCarousel({skipSnaps: true})
18+
const Carousel: FC<CarouselProps> = ({slides, startIndex = 0}) => {
19+
const [emblaRef] = useEmblaCarousel({skipSnaps: true, startIndex})
1920

2021
return (
2122
<div className="overflow-hidden" ref={emblaRef}>

frontend/src/context/PlaybackContext.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export const PlaybackContextProvider: FC<PlaybackContextProviderProps> = ({
2323
children,
2424
}) => {
2525
const [playbackRefetchInterval, setPlaybackRefetchInterval] = useState(10000);
26-
const { data: playbackInfo } = useQuery<PlaybackInfo>({
26+
const { data: playbackInfo, isError } = useQuery<PlaybackInfo>({
2727
queryKey: ["playbackInfo"],
2828
queryFn: () => {
2929
return getPlaybackInfo();
@@ -33,8 +33,8 @@ export const PlaybackContextProvider: FC<PlaybackContextProviderProps> = ({
3333
refetchIntervalInBackground: false,
3434
});
3535
useEffect(() => {
36-
setPlaybackRefetchInterval(playbackInfo ? 10000 : 20000);
37-
}, [playbackInfo]);
36+
if (isError) {setPlaybackRefetchInterval(30000)}
37+
}, [isError]);
3838

3939
return (
4040
<PlaybackContext.Provider value={{ playbackInfo }}>

frontend/src/playlistExplorer/AlbumList/AlbumContainer.tsx

Lines changed: 5 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,30 @@
1-
import React, { FC, useEffect, useRef, useState } from "react";
1+
import React, { FC, useState } from "react";
22
import { Album } from "../../interfaces/Album";
33
import PlaylistIcon from "../../components/PlaylistIcon";
44
import { RotatingBorderBox } from "../../components/RotatingBorderBox";
5-
import Modal from "../../components/Modal";
6-
import { Playlist } from "../../interfaces/Playlist";
7-
import { addAlbumToPlaylist, startPlayback } from "../../api";
8-
import { useModal } from "../../hooks/useModal";
9-
import Button from "../../components/Button";
105

116
interface AlbumContainerProps {
127
album: Album;
13-
contextPlaylist: Playlist
14-
associatedPlaylists: Playlist[];
8+
onClick: (album: Album) => void;
159
active?: boolean;
1610
}
1711

1812
export const AlbumContainer: FC<AlbumContainerProps> = ({
1913
album,
20-
contextPlaylist,
21-
associatedPlaylists,
14+
onClick,
2215
active,
2316
}) => {
24-
const ref = useRef<HTMLDivElement>(null);
25-
useEffect(() => {
26-
if (active && ref.current) {
27-
ref.current.scrollIntoView({ behavior: "smooth", block: "center" });
28-
}
29-
}, [active]);
3017
const [showMoreInfo, setShowMoreInfo] = useState(false);
31-
const { isModalOpen, openModal, closeModal } = useModal();
3218
return (
33-
<>
34-
<Modal isModalOpen={isModalOpen} closeModal={closeModal}>
35-
<AlbumActionsModalContent
36-
contextPlaylist={contextPlaylist}
37-
associatedPlaylists={associatedPlaylists}
38-
album={album}
39-
closeModal={closeModal}
40-
/>
41-
</Modal>
4219
<div
43-
className={`group max-h-80 max-w-80 [perspective:1000px]`}
20+
className={`group max-h-40 max-w-40 [perspective:1000px]`}
4421
onClick={() => {
4522
setShowMoreInfo((current) => !current);
23+
onClick(album)
4624
}}
4725
>
4826
<RotatingBorderBox active={active}>
4927
<div
50-
ref={ref}
5128
className={`m-1 relative transition-all duration-500 [transform-style:preserve-3d] ${
5229
showMoreInfo && "[transform:rotateY(180deg)]"
5330
}`}
@@ -57,85 +34,12 @@ export const AlbumContainer: FC<AlbumContainerProps> = ({
5734
<div className="absolute top-0 [transform:rotateY(180deg)] [backface-visibility:hidden]">
5835
<div className="flex flex-col space-y-2 m-2">
5936
<div>{album.name}</div>
60-
<div>
61-
{album.artists.map((artist) => artist.name).join(", ")}
62-
</div>
63-
<div>{album.genres}</div>
64-
<div>{album.label}</div>
65-
<div>{album.popularity}</div>
66-
<button
67-
onClick={(e) => {
68-
e.stopPropagation();
69-
openModal();
70-
}}
71-
>
72-
Actions
73-
</button>
7437
</div>
7538
</div>
7639
)}
7740
</div>
7841
</RotatingBorderBox>
7942
</div>
80-
</>
81-
);
82-
};
83-
84-
interface AlbumActionsModalContentProps {
85-
album: Album;
86-
contextPlaylist: Playlist
87-
associatedPlaylists: Playlist[];
88-
closeModal: () => void;
89-
}
90-
const AlbumActionsModalContent: FC<AlbumActionsModalContentProps> = ({
91-
album,
92-
contextPlaylist,
93-
associatedPlaylists,
94-
closeModal,
95-
}) => {
96-
const addAlbumToAssociatedPlaylist = (targetPlaylist: Playlist): void => {
97-
addAlbumToPlaylist(targetPlaylist.id, album.id);
98-
closeModal();
99-
};
100-
return (
101-
<div>
102-
<div className="flex flex-row justify-between">
103-
<h2 className="my-auto text-m">Actions:</h2>
104-
<button onClick={closeModal}>X</button>
105-
</div>
106-
<div className="flex flex-col my-2 space-y-2">
107-
{associatedPlaylists.map((associatedPlaylist) => (
108-
<Button
109-
onClick={() => addAlbumToAssociatedPlaylist(associatedPlaylist)}
110-
key={associatedPlaylist.id}
111-
>
112-
Add to {associatedPlaylist.name}
113-
</Button>
114-
))}
115-
<Button
116-
onClick={() => (startPlayback({context_uri: contextPlaylist.uri, offset: {album_id: album.id} }))}
117-
>
118-
Play Album
119-
</Button>
120-
</div>
121-
</div>
122-
);
123-
};
124-
125-
export const AlbumInfo: FC<AlbumContainerProps> = ({ album }) => {
126-
return (
127-
<div className="relative">
128-
<AlbumCover album={album} blur />
129-
<div className="absolute top-0">
130-
<div className="flex flex-col space-y-2 m-2">
131-
<div>{album.name}</div>
132-
<div>{album.artists.map((artist) => artist.name).join(", ")}</div>
133-
<div>{album.genres}</div>
134-
<div>{album.label}</div>
135-
<div>{album.popularity}</div>
136-
</div>
137-
</div>
138-
</div>
13943
);
14044
};
14145

Lines changed: 46 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1-
import React, { FC } from "react";
1+
import React, { FC, useState } from "react";
22
import { Album } from "../../interfaces/Album";
33
import { AlbumContainer } from "./AlbumContainer";
4-
import Box from "../../components/Box";
54
import { Playlist } from "../../interfaces/Playlist";
5+
import Carousel from "../../components/Carousel/Carousel";
6+
import Button from "../../components/Button";
7+
import { addAlbumToPlaylist, startPlayback } from "../../api";
68

79
interface AlbumListProps {
810
albumList: Album[];
@@ -16,19 +18,54 @@ export const AlbumList: FC<AlbumListProps> = ({
1618
associatedPlaylists,
1719
activeAlbumId,
1820
}) => {
21+
const activeAlbumIndex = albumList.findIndex((album) => album.id === activeAlbumId);
22+
const [selectedAlbum, setSelectedAlbum] = useState<Album | undefined>(undefined)
23+
const onAlbumClick = (album: Album) => {
24+
if (selectedAlbum && selectedAlbum.id == album.id) {
25+
setSelectedAlbum(undefined)
26+
} else {
27+
setSelectedAlbum(album)
28+
}
29+
}
30+
1931
return (
20-
<Box>
21-
<div className="grid grid-cols-2 gap-2 sm:mx-24 h-[60vh] overflow-auto">
22-
{albumList.map((album) => (
32+
<div>
33+
<Carousel startIndex={activeAlbumIndex != -1 ? activeAlbumIndex : 0} slides={albumList.map((album) => (
2334
<AlbumContainer
2435
album={album}
2536
key={album.id}
26-
contextPlaylist={contextPlaylist}
27-
associatedPlaylists={associatedPlaylists}
2837
active={album.id == activeAlbumId}
38+
onClick={onAlbumClick}
2939
/>
40+
)
41+
)}/>
42+
{selectedAlbum &&
43+
<div className="flex">
44+
<div className="flex flex-col">
45+
<div>album: {selectedAlbum.name}</div>
46+
<div>
47+
artists: {selectedAlbum.artists.map((artist) => artist.name).join(", ")}
48+
</div>
49+
<div>genres: {selectedAlbum.genres}</div>
50+
<div>label: {selectedAlbum.label}</div>
51+
</div>
52+
<div className="flex flex-col">
53+
{associatedPlaylists.map((associatedPlaylist) => (
54+
<Button
55+
onClick={() => addAlbumToPlaylist(associatedPlaylist.id, selectedAlbum.id)}
56+
key={associatedPlaylist.id}
57+
>
58+
Add to {associatedPlaylist.name}
59+
</Button>
3060
))}
31-
</div>
32-
</Box>
61+
<Button
62+
onClick={() => (startPlayback({context_uri: contextPlaylist.uri, offset: {album_id: selectedAlbum.id} }))}
63+
>
64+
Play Album
65+
</Button>
66+
</div>
67+
</div>}
68+
</div>
3369
);
70+
3471
};

frontend/src/playlistExplorer/PlaylistExplorer.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ import InputLabel from "../components/InputLabel";
66
import Button from "../components/Button";
77
import { Form, useForm } from "react-hook-form";
88
import {
9-
findAssociatedPlaylists,
109
getPlaylistAlbums,
1110
getPlaylistTracks,
11+
playlistSearch,
1212
updatePlaylist,
1313
} from "../api";
1414
import { useQuery } from "@tanstack/react-query";
@@ -53,9 +53,9 @@ export const PlaylistExplorer: FC = () => {
5353

5454
useEffect(() => {
5555
if (playlist.name.slice(0, 10) === "New Albums") {
56-
findAssociatedPlaylists(playlist).then(
56+
playlistSearch(playlist.name.slice(11)).then(
5757
(associatedPlaylists: Playlist[]) => {
58-
setAssociatedPlaylists(associatedPlaylists);
58+
setAssociatedPlaylists(associatedPlaylists.filter((associatedPlaylist) => associatedPlaylist.name !== playlist.name));
5959
}
6060
);
6161
}

0 commit comments

Comments
 (0)